From 446f16bc33beabb59ae337c799451c1b9c6d7c23 Mon Sep 17 00:00:00 2001 From: D3ATHBRINGER13 <53559772+D3ATHBRINGER13@users.noreply.github.com> Date: Mon, 4 Jan 2021 19:14:55 +0000 Subject: [PATCH 01/37] Update LICENSE to 2021 (#1799) --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index acd4af141a1..0e368d546cd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2019-2020 GeyserMC. http://geysermc.org +Copyright (c) 2019-2021 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 From 69dc4a964427d25c816b89a7814f24654a57c738 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 4 Jan 2021 15:54:52 -0500 Subject: [PATCH 02/37] [ci-skip] Improve README for better information (#1702) --- .github/ISSUE_TEMPLATE/bug_report.md | 47 +++++++++++++++++++--------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 326a1ebd575..17e88f26887 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,34 +7,51 @@ assignees: '' --- + + - + **Describe the bug** - + +A clear and concise description of what the bug is. **To Reproduce** - - - - - + +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error **Expected behavior** - + +A clear and concise description of what you expected to happen. **Screenshots / Videos** - -**Server Version** - +If applicable, add screenshots to help explain your problem. + +**Server Version and Plugins** + +If you just run Geyser-Spigot, you can leave this area blank as the next section covers this information. -**Geyser Version** - +If you're running a multi-server instance, or using Geyser Standalone: + +- Give us the exact output from `/version` on all servers involved. Saying "latest" does not help us at all. +- Please list all plugins on all servers involved. + +If this bug occurs on a server you do not control, please fill this in to the best of your knowledge. + +**Geyser Dump** + +If Geyser starts correctly, please also include the link to a dump by using `/geyser dump`. If you use the Standalone GUI, the option can be found under `Commands` => `Dump`. This provides us information about your server that we can use to debug your issue. **Minecraft: Bedrock Edition Version** - + +The version of your Minecraft: Bedrock Edition client you tested with, along with your device type (e.g. Windows 10, Switch...). **Additional Context** - + +Add any other context about the problem here. From 29e47cf636fe0520012dfe6754b56f2afb31a735 Mon Sep 17 00:00:00 2001 From: Nickg two Date: Mon, 4 Jan 2021 19:24:07 -0700 Subject: [PATCH 03/37] [ci skip] changed a 2020 to 2021 (#1801) lol imagine being in 2020 --- licenseheader.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/licenseheader.txt b/licenseheader.txt index c22c426c48e..8ef205a31e8 100644 --- a/licenseheader.txt +++ b/licenseheader.txt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 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 From b6389317f0c1aeed50fdea242ce82ad02140fd99 Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Tue, 5 Jan 2021 09:45:01 -0900 Subject: [PATCH 04/37] Fix minor bug in auth form (#1806) --- .../org/geysermc/connector/utils/LoginEncryptionUtils.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 9e16c428dda..5c212ba02c0 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -198,12 +198,12 @@ public static boolean authenticateFromForm(GeyserSession session, GeyserConnecto String password = response.getInputResponses().get(2); session.authenticate(email, password); + + // Clear windows so authentication data isn't accidentally cached + windowCache.getWindows().clear(); } else { showLoginDetailsWindow(session); } - - // Clear windows so authentication data isn't accidentally cached - windowCache.getWindows().clear(); } else if (formId == AUTH_FORM_ID && window instanceof SimpleFormWindow) { SimpleFormResponse response = (SimpleFormResponse) window.getResponse(); if (response != null) { From 0641800be7a460f5bd0d256796e5f1e4c48dc4c2 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 5 Jan 2021 18:41:20 -0500 Subject: [PATCH 05/37] Add Tickable interface (#1790) * Add Tickable interface By having a tickable interface, we're only dedicating one thread to ticking entities and running tasks as opposed to several. This will also help with implementing world border support. * removeEntity already clears tickableEntities for us * Only tick the entity if it's not being ticked --- .../entity/ItemedFireballEntity.java | 6 +- .../connector/entity/ThrowableEntity.java | 38 +++----- .../geysermc/connector/entity/Tickable.java | 35 +++++++ .../living/monster/EnderDragonEntity.java | 21 ++--- .../network/session/GeyserSession.java | 39 +++++++- .../network/session/cache/EntityCache.java | 24 ++++- .../player/BedrockMovePlayerTranslator.java | 94 +------------------ .../collision/CollisionManager.java | 64 +++++++++++++ .../connector/utils/DimensionUtils.java | 4 - 9 files changed, 183 insertions(+), 142 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/Tickable.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java index 488c0e90ce1..2b411109add 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java @@ -38,11 +38,11 @@ public ItemedFireballEntity(long entityId, long geyserId, EntityType entityType, } @Override - protected void updatePosition(GeyserSession session) { + public void tick(GeyserSession session) { position = position.add(motion); // TODO: While this reduces latency in position updating (needed for better fireball reflecting), - // TODO: movement is incredibly stiff. See if the MoveEntityDeltaPacket in 1.16.100 fixes this, and if not, - // TODO: only use this laggy movement for fireballs that be reflected + // TODO: movement is incredibly stiff. + // TODO: Only use this laggy movement for fireballs that be reflected moveAbsoluteImmediate(session, position, rotation, false, true); float drag = getDrag(session); motion = motion.add(acceleration).mul(drag); diff --git a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java index 553e558eaba..4e0c25ab528 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java @@ -33,50 +33,35 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - /** * Used as a class for any object-like entity that moves as a projectile */ -public class ThrowableEntity extends Entity { +public class ThrowableEntity extends Entity implements Tickable { private Vector3f lastPosition; - /** - * Updates the position for the Bedrock client. - * - * Java clients assume the next positions of moving items. Bedrock needs to be explicitly told positions - */ - protected ScheduledFuture positionUpdater; public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); this.lastPosition = position; } + /** + * Updates the position for the Bedrock client. + * + * Java clients assume the next positions of moving items. Bedrock needs to be explicitly told positions + */ @Override - public void spawnEntity(GeyserSession session) { - super.spawnEntity(session); - positionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> { - if (session.isClosed()) { - positionUpdater.cancel(true); - return; - } - updatePosition(session); - }, 0, 50, TimeUnit.MILLISECONDS); - } - - protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { - super.moveAbsolute(session, position, rotation, isOnGround, teleported); - } - - protected void updatePosition(GeyserSession session) { + public void tick(GeyserSession session) { super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround); float drag = getDrag(session); float gravity = getGravity(); motion = motion.mul(drag).down(gravity); } + protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { + super.moveAbsolute(session, position, rotation, isOnGround, teleported); + } + /** * Get the gravity of this entity type. Used for applying gravity while the entity is in motion. * @@ -140,7 +125,6 @@ protected boolean isInWater(GeyserSession session) { @Override public boolean despawnEntity(GeyserSession session) { - positionUpdater.cancel(true); if (entityType == EntityType.THROWN_ENDERPEARL) { LevelEventPacket particlePacket = new LevelEventPacket(); particlePacket.setType(LevelEventType.PARTICLE_TELEPORT); diff --git a/connector/src/main/java/org/geysermc/connector/entity/Tickable.java b/connector/src/main/java/org/geysermc/connector/entity/Tickable.java new file mode 100644 index 00000000000..a7d571ccbd7 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/Tickable.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity; + +import org.geysermc.connector.network.session.GeyserSession; + +/** + * Implemented onto anything that should have code ran every Minecraft tick - 50 milliseconds. + */ +public interface Tickable { + void tick(GeyserSession session); +} 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 7dbd96a44c5..df9456826a7 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 @@ -33,14 +33,12 @@ import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import lombok.Data; +import org.geysermc.connector.entity.Tickable; 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 { +public class EnderDragonEntity extends InsentientEntity implements Tickable { /** * The Ender Dragon has multiple hit boxes, which * are each its own invisible entity @@ -63,8 +61,6 @@ public class EnderDragonEntity extends InsentientEntity { 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); @@ -130,24 +126,23 @@ public void spawnEntity(GeyserSession session) { 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); } + @Override + public void tick(GeyserSession session) { + pushSegment(); + updateBoundingBoxes(session); + } + /** * Updates the positions of the Ender Dragon's multiple bounding boxes * 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 375c747d45c..5a0d5eeb1b3 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 @@ -35,6 +35,7 @@ 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.packet.handshake.client.HandshakePacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket; import com.github.steveice10.mc.protocol.packet.login.server.LoginSuccessPacket; @@ -64,6 +65,7 @@ import org.geysermc.common.window.CustomFormWindow; import org.geysermc.common.window.FormWindow; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; @@ -94,6 +96,7 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; @Getter public class GeyserSession implements CommandSender { @@ -245,10 +248,10 @@ public class GeyserSession implements CommandSender { private ScheduledFuture bucketScheduledFuture; /** - * Sends a movement packet every three seconds if the player hasn't moved. Prevents timeouts when AFK in certain instances. + * Used to send a movement packet every three seconds if the player hasn't moved. Prevents timeouts when AFK in certain instances. */ @Setter - private ScheduledFuture movementSendIfIdle; + private long lastMovementTimestamp = System.currentTimeMillis(); /** * Controls whether the daylight cycle gamerule has been sent to the client, so the sun/moon remain motionless. @@ -328,6 +331,11 @@ public class GeyserSession implements CommandSender { private List selectedEmotes = new ArrayList<>(); private final Set emotes = new HashSet<>(); + /** + * The thread that will run every 50 milliseconds - one Minecraft tick. + */ + private ScheduledFuture tickThread = null; + private MinecraftProtocol protocol; public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { @@ -458,6 +466,9 @@ public void authenticate(String username, String password) { connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); } + // Start ticking + tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS); + downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory()); if (connector.getConfig().getRemote().isUseProxyProtocol()) { downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true); @@ -584,6 +595,8 @@ public void disconnect(String reason) { } } + tickThread.cancel(true); + this.chunkCache = null; this.entityCache = null; this.effectCache = null; @@ -598,6 +611,28 @@ public void close() { disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.close", getClientData().getLanguageCode())); } + /** + * Called every 50 milliseconds - one Minecraft tick. + */ + public void tick() { + // Check to see if the player's position needs updating - a position update should be sent once every 3 seconds + if (spawned && (System.currentTimeMillis() - lastMovementTimestamp) > 3000) { + // Recalculate in case something else changed position + Vector3d position = collisionManager.adjustBedrockPosition(playerEntity.getPosition(), playerEntity.isOnGround()); + // A null return value cancels the packet + if (position != null) { + ClientPlayerPositionPacket packet = new ClientPlayerPositionPacket(playerEntity.isOnGround(), + position.getX(), position.getY(), position.getZ()); + sendDownstreamPacket(packet); + } + lastMovementTimestamp = System.currentTimeMillis(); + } + + for (Tickable entity : entityCache.getTickableEntities()) { + entity.tick(this); + } + } + public void setAuthenticationData(AuthData authData) { this.authData = authData; } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java index 62b0dbd6b90..40000551c42 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java @@ -28,6 +28,7 @@ import it.unimi.dsi.fastutil.longs.*; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import lombok.Getter; +import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.player.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; @@ -40,17 +41,21 @@ * for that player (e.g. seeing vanished players from /vanish) */ public class EntityCache { - private GeyserSession session; + private final GeyserSession session; @Getter private Long2ObjectMap entities = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); + /** + * A list of all entities that must be ticked. + */ + private final List tickableEntities = Collections.synchronizedList(new ArrayList<>()); private Long2LongMap entityIdTranslations = Long2LongMaps.synchronize(new Long2LongOpenHashMap()); private Map playerEntities = Collections.synchronizedMap(new HashMap<>()); private Map bossBars = Collections.synchronizedMap(new HashMap<>()); - private Long2LongMap cachedPlayerEntityLinks = Long2LongMaps.synchronize(new Long2LongOpenHashMap()); + private final Long2LongMap cachedPlayerEntityLinks = Long2LongMaps.synchronize(new Long2LongOpenHashMap()); @Getter - private AtomicLong nextEntityId = new AtomicLong(2L); + private final AtomicLong nextEntityId = new AtomicLong(2L); public EntityCache(GeyserSession session) { this.session = session; @@ -59,6 +64,11 @@ public EntityCache(GeyserSession session) { public void spawnEntity(Entity entity) { if (cacheEntity(entity)) { entity.spawnEntity(session); + + if (entity instanceof Tickable) { + // Start ticking it + tickableEntities.add((Tickable) entity); + } } } @@ -76,6 +86,10 @@ public boolean removeEntity(Entity entity, boolean force) { if (entity != null && entity.isValid() && (force || entity.despawnEntity(session))) { long geyserId = entityIdTranslations.remove(entity.getEntityId()); entities.remove(geyserId); + + if (entity instanceof Tickable) { + tickableEntities.remove(entity); + } return true; } return false; @@ -152,4 +166,8 @@ public long getCachedPlayerEntityLink(long playerId) { public void addCachedPlayerEntityLink(long playerId, long linkedEntityId) { cachedPlayerEntityLinks.put(playerId, linkedEntityId); } + + public List getTickableEntities() { + return tickableEntities; + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java index 2d6fae3e1d2..5b4136a6b69 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java @@ -33,16 +33,12 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; -import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.entity.player.PlayerEntity; 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.collision.CollisionManager; - -import java.util.concurrent.TimeUnit; @Translator(packet = MovePlayerPacket.class) public class BedrockMovePlayerTranslator extends PacketTranslator { @@ -63,9 +59,7 @@ public void translate(MovePlayerPacket packet, GeyserSession session) { return; } - if (session.getMovementSendIfIdle() != null) { - session.getMovementSendIfIdle().cancel(true); - } + session.setLastMovementTimestamp(System.currentTimeMillis()); if (session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityType.PLAYER.getOffset(), 0))) { // head yaw, pitch, head yaw @@ -86,7 +80,7 @@ public void translate(MovePlayerPacket packet, GeyserSession session) { session.sendDownstreamPacket(playerRotationPacket); } else { - Vector3d position = adjustBedrockPosition(session, packet.getPosition(), packet.isOnGround()); + Vector3d position = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.isOnGround()); if (position != null) { // A null return value cancels the packet if (isValidMove(session, packet.getMode(), entity.getPosition(), packet.getPosition())) { Packet movePacket; @@ -128,7 +122,7 @@ public void translate(MovePlayerPacket packet, GeyserSession session) { } else { // Not a valid move session.getConnector().getLogger().debug("Recalculating position..."); - recalculatePosition(session); + session.getCollisionManager().recalculatePosition(); } } } @@ -141,13 +135,9 @@ public void translate(MovePlayerPacket packet, GeyserSession session) { if (entity.getRightParrot() != null) { entity.getRightParrot().moveAbsolute(session, entity.getPosition(), entity.getRotation(), true, false); } - - // Schedule a position send loop if the player is idle - session.setMovementSendIfIdle(session.getConnector().getGeneralThreadPool().schedule(() -> sendPositionIfIdle(session), - 3, TimeUnit.SECONDS)); } - public boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Vector3f currentPosition, Vector3f newPosition) { + private boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Vector3f currentPosition, Vector3f newPosition) { if (mode != MovePlayerPacket.Mode.NORMAL) return true; @@ -171,81 +161,5 @@ public boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Ve return true; } - - /** - * Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between - * the two versions. - * - * @param session the current GeyserSession - * @param bedrockPosition the current Bedrock position of the client - * @param onGround whether the Bedrock player is on the ground - * @return the position to send to the Java server, or null to cancel sending the packet - */ - private Vector3d adjustBedrockPosition(GeyserSession session, Vector3f bedrockPosition, boolean onGround) { - // We need to parse the float as a string since casting a float to a double causes us to - // lose precision and thus, causes players to get stuck when walking near walls - double javaY = bedrockPosition.getY() - EntityType.PLAYER.getOffset(); - - Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY, - Double.parseDouble(Float.toString(bedrockPosition.getZ()))); - - if (session.getConnector().getConfig().isCacheChunks()) { - // With chunk caching, we can do some proper collision checks - CollisionManager collisionManager = session.getCollisionManager(); - collisionManager.updatePlayerBoundingBox(position); - - // Correct player position - if (!collisionManager.correctPlayerPosition()) { - // Cancel the movement if it needs to be cancelled - recalculatePosition(session); - return null; - } - - position = Vector3d.from(collisionManager.getPlayerBoundingBox().getMiddleX(), - collisionManager.getPlayerBoundingBox().getMiddleY() - (collisionManager.getPlayerBoundingBox().getSizeY() / 2), - collisionManager.getPlayerBoundingBox().getMiddleZ()); - } else { - // When chunk caching is off, we have to rely on this - // It rounds the Y position up to the nearest 0.5 - // This snaps players to snap to the top of stairs and slabs like on Java Edition - // However, it causes issues such as the player floating on carpets - if (onGround) javaY = Math.ceil(javaY * 2) / 2; - position = position.up(javaY - position.getY()); - } - - return position; - } - - // TODO: This makes the player look upwards for some reason, rotation values must be wrong - public void recalculatePosition(GeyserSession session) { - PlayerEntity entity = session.getPlayerEntity(); - // Gravity might need to be reset... - 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.NORMAL); - session.sendUpstreamPacket(movePlayerPacket); - } - - private void sendPositionIfIdle(GeyserSession session) { - if (session.isClosed()) return; - PlayerEntity entity = session.getPlayerEntity(); - // Recalculate in case something else changed position - Vector3d position = adjustBedrockPosition(session, entity.getPosition(), entity.isOnGround()); - // A null return value cancels the packet - if (position != null) { - ClientPlayerPositionPacket packet = new ClientPlayerPositionPacket(session.getPlayerEntity().isOnGround(), - position.getX(), position.getY(), position.getZ()); - session.sendDownstreamPacket(packet); - } - session.setMovementSendIfIdle(session.getConnector().getGeneralThreadPool().schedule(() -> sendPositionIfIdle(session), - 3, TimeUnit.SECONDS)); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java index 22e5c95fd2b..203e4406f3a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java @@ -30,8 +30,12 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityFlags; +import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import lombok.Getter; import lombok.Setter; +import org.geysermc.connector.entity.player.PlayerEntity; +import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.collision.translators.BlockCollision; @@ -105,6 +109,7 @@ public void updatePlayerBoundingBox() { // According to the Minecraft Wiki, when sneaking: // - In Bedrock Edition, the height becomes 1.65 blocks, allowing movement through spaces as small as 1.75 (2 - 1⁄4) blocks high. // - In Java Edition, the height becomes 1.5 blocks. + // TODO: Have this depend on the player's literal bounding box variable if (session.isSneaking()) { playerBoundingBox.setSizeY(1.5); } else { @@ -113,6 +118,65 @@ public void updatePlayerBoundingBox() { } } + /** + * Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between + * the two versions. + * + * @param bedrockPosition the current Bedrock position of the client + * @param onGround whether the Bedrock player is on the ground + * @return the position to send to the Java server, or null to cancel sending the packet + */ + public Vector3d adjustBedrockPosition(Vector3f bedrockPosition, boolean onGround) { + // We need to parse the float as a string since casting a float to a double causes us to + // lose precision and thus, causes players to get stuck when walking near walls + double javaY = bedrockPosition.getY() - EntityType.PLAYER.getOffset(); + + Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY, + Double.parseDouble(Float.toString(bedrockPosition.getZ()))); + + if (session.getConnector().getConfig().isCacheChunks()) { + // With chunk caching, we can do some proper collision checks + updatePlayerBoundingBox(position); + + // Correct player position + if (!correctPlayerPosition()) { + // Cancel the movement if it needs to be cancelled + recalculatePosition(); + return null; + } + + position = Vector3d.from(playerBoundingBox.getMiddleX(), + playerBoundingBox.getMiddleY() - (playerBoundingBox.getSizeY() / 2), + playerBoundingBox.getMiddleZ()); + } else { + // When chunk caching is off, we have to rely on this + // It rounds the Y position up to the nearest 0.5 + // This snaps players to snap to the top of stairs and slabs like on Java Edition + // However, it causes issues such as the player floating on carpets + if (onGround) javaY = Math.ceil(javaY * 2) / 2; + position = position.up(javaY - position.getY()); + } + + return position; + } + + // TODO: This makes the player look upwards for some reason, rotation values must be wrong + public void recalculatePosition() { + PlayerEntity entity = session.getPlayerEntity(); + // Gravity might need to be reset... + 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.NORMAL); + session.sendUpstreamPacket(movePlayerPacket); + } + public List getPlayerCollidableBlocks() { List blocks = new ArrayList<>(); diff --git a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java index f330aed671f..f193a61db35 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java @@ -58,10 +58,6 @@ public static void switchDimension(GeyserSession session, String javaDimension) int bedrockDimension = javaToBedrock(javaDimension); Entity player = session.getPlayerEntity(); - if (session.getMovementSendIfIdle() != null) { - session.getMovementSendIfIdle().cancel(true); - } - session.getEntityCache().removeAllEntities(); session.getItemFrameCache().clear(); session.getSkullCache().clear(); From 92c86cf15b3f086d0c6468415571a357dd8c1170 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 5 Jan 2021 23:28:31 -0500 Subject: [PATCH 06/37] Null check tick thread (#1812) --- .../org/geysermc/connector/network/session/GeyserSession.java | 4 +++- 1 file changed, 3 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 5a0d5eeb1b3..1a51bf97056 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 @@ -595,7 +595,9 @@ public void disconnect(String reason) { } } - tickThread.cancel(true); + if (tickThread != null) { + tickThread.cancel(true); + } this.chunkCache = null; this.entityCache = null; From bbbb7034f654ed48cbf0864033a6348d5b92f6ec Mon Sep 17 00:00:00 2001 From: rtm516 Date: Thu, 7 Jan 2021 12:21:35 +0000 Subject: [PATCH 07/37] Trigger builds of Geyser-Fabric and GeyserAndroid after a build success (#1815) * Trigger builds of Geyser-Fabric and GeyserAndroid after a build success * Enable GeyserAndroid and fix paths * Only build for master * Disable propagation of build results * Don't wait for sequential builds --- Jenkinsfile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index e7f2ec4e2c1..d69e851da68 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -69,5 +69,13 @@ pipeline { discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.opencollab.dev/job/GeyserMC/job/Geyser)", footer: 'Open Collaboration Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK } } + success { + script { + if (env.BRANCH_NAME == 'master') { + build propagate: false, wait: false, job: 'GeyserMC/Geyser-Fabric/java-1.16' + build propagate: false, wait: false, job: 'GeyserMC/GeyserAndroid/master' + } + } + } } } From dc46905e503de11aa1a0fb951375b257ba8564be Mon Sep 17 00:00:00 2001 From: SupremeMortal <6178101+SupremeMortal@users.noreply.github.com> Date: Thu, 7 Jan 2021 19:51:40 +0000 Subject: [PATCH 08/37] Use Artifactory Jenkins plugin for deployment (#1818) * Use artifactory jenkins plugin * Bump version to 1.2.0-SNAPSHOT --- Jenkinsfile | 22 +++++++++++++++++++++- pom.xml | 13 ------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index d69e851da68..50149136155 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -26,7 +26,27 @@ pipeline { } steps { - sh 'mvn javadoc:jar source:jar deploy -DskipTests' + rtMavenDeployer( + id: "maven-deployer", + serverId: "opencollab-artifactory", + releaseRepo: "maven-releases", + snapshotRepo: "maven-snapshots" + ) + rtMavenResolver( + id: "maven-resolver", + serverId: "opencollab-artifactory", + releaseRepo: "release", + snapshotRepo: "snapshot" + ) + rtMavenRun( + pom: 'pom.xml', + goals: 'javadoc:jar source:jar install -DskipTests', + deployerId: "maven-deployer", + resolverId: "maven-resolver" + ) + rtPublishBuildInfo( + serverId: "opencollab-artifactory" + ) } } } diff --git a/pom.xml b/pom.xml index 1b544f9eee1..011b320f48c 100644 --- a/pom.xml +++ b/pom.xml @@ -71,19 +71,6 @@ - - - releases - opencollab-releases - https://repo.opencollab.dev/maven-releases - - - snapshots - opencollab-snapshots - https://repo.opencollab.dev/maven-snapshots - - - org.projectlombok From 7cefb5713e0aaf366fde30389998baf02854b9b8 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 7 Jan 2021 16:50:57 -0500 Subject: [PATCH 09/37] Clarify that enabling Xbox achievements blocks all commands (#1810) --- connector/src/main/resources/config.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index ac9ec753d83..42e004db148 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -132,8 +132,7 @@ above-bedrock-nether-building: false force-resource-packs: true # Allows Xbox achievements to be unlocked. -# This disables certain commands so the Bedrock client can't to "cheat" to get them. -# Commands such as /gamemode and /give will not work from Bedrock with this enabled +# THIS DISABLES ALL COMMANDS FROM SUCCESSFULLY RUNNING FOR BEDROCK IN-GAME, as otherwise Bedrock thinks you are cheating. xbox-achievements-enabled: false # bStats is a stat tracker that is entirely anonymous and tracks only basic information From fe23c790535d692415e329506d12b8f763fc4844 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 7 Jan 2021 19:40:34 -0500 Subject: [PATCH 10/37] Implement book editing (#1117) * Implement book editing Updates the PR created by @ForceUpdate1 for 1.16 support. Seems to work fine now that hand support is in MCProtocolLib. Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> * Remove debug line * Simplify code Currently still borked for creative mode. * Fix books on creative * Bug fixes * Fix NPE? * Blind fixes * Send Book update before any player actions * Remove debug prints * Fix out of bounds for page replace and add * Fix editing desync and remove empty pages from the end * Send edit packet after signing * Refactor * Clean up and fix creative * Apply suggestions from code review Co-authored-by: rtm516 Co-authored-by: ForceUpdate1 Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Co-authored-by: David Choo Co-authored-by: rtm516 --- .../network/session/GeyserSession.java | 3 + .../network/session/cache/BookEditCache.java | 75 +++++++++++ .../bedrock/BedrockBookEditTranslator.java | 123 ++++++++++++++++++ ...BedrockInventoryTransactionTranslator.java | 3 + .../BedrockMobEquipmentTranslator.java | 3 + .../player/BedrockActionTranslator.java | 5 + .../player/BedrockMovePlayerTranslator.java | 3 + .../translators/item/ItemRegistry.java | 7 + .../translators/nbt/BookPagesTranslator.java | 3 +- 9 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBookEditTranslator.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 1a51bf97056..8a0362663e5 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 @@ -113,6 +113,7 @@ public class GeyserSession implements CommandSender { private final SessionPlayerEntity playerEntity; private PlayerInventory inventory; + private BookEditCache bookEditCache; private ChunkCache chunkCache; private EntityCache entityCache; private EntityEffectCache effectCache; @@ -342,6 +343,7 @@ public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServ this.connector = connector; this.upstream = new UpstreamSession(bedrockServerSession); + this.bookEditCache = new BookEditCache(this); this.chunkCache = new ChunkCache(this); this.entityCache = new EntityCache(this); this.effectCache = new EntityEffectCache(); @@ -599,6 +601,7 @@ public void disconnect(String reason) { tickThread.cancel(true); } + this.bookEditCache = null; this.chunkCache = null; this.entityCache = null; this.effectCache = null; diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java new file mode 100644 index 00000000000..f81a9fdf906 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019-2021 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.session.cache; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientEditBookPacket; +import lombok.Setter; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemRegistry; + +/** + * Manages updating the current writable book. + * + * Java sends book updates less frequently than Bedrock, and this can cause issues with servers that rate limit + * book packets. Because of this, we need to ensure packets are only send every second or so at maximum. + */ +public class BookEditCache { + private final GeyserSession session; + @Setter + private ClientEditBookPacket packet; + /** + * Stores the last time a book update packet was sent to the server. + */ + private long lastBookUpdate; + + public BookEditCache(GeyserSession session) { + this.session = session; + } + + /** + * Check to see if there is a book edit update to send, and if so, send it. + */ + public void checkForSend() { + if (packet == null) { + // No new packet has to be sent + return; + } + // Prevent kicks due to rate limiting - specifically on Spigot servers + if ((System.currentTimeMillis() - lastBookUpdate) < 1000) { + return; + } + // Don't send the update if the player isn't not holding a book, shouldn't happen if we catch all interactions + ItemStack itemStack = session.getInventory().getItemInHand(); + if (itemStack == null || itemStack.getId() != ItemRegistry.WRITABLE_BOOK.getJavaId()) { + packet = null; + return; + } + session.getDownstream().getSession().send(packet); + packet = null; + lastBookUpdate = System.currentTimeMillis(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBookEditTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBookEditTranslator.java new file mode 100644 index 00000000000..dd5d08a2c39 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBookEditTranslator.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2019-2021 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.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientEditBookPacket; +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.opennbt.tag.builtin.Tag; +import com.nukkitx.protocol.bedrock.packet.BookEditPacket; +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.inventory.InventoryTranslator; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +@Translator(packet = BookEditPacket.class) +public class BedrockBookEditTranslator extends PacketTranslator { + + @Override + public void translate(BookEditPacket packet, GeyserSession session) { + ItemStack itemStack = session.getInventory().getItemInHand(); + if (itemStack != null) { + CompoundTag tag = itemStack.getNbt() != null ? itemStack.getNbt() : new CompoundTag(""); + ItemStack bookItem = new ItemStack(itemStack.getId(), itemStack.getAmount(), tag); + List pages = tag.contains("pages") ? new LinkedList<>(((ListTag) tag.get("pages")).getValue()) : new LinkedList<>(); + + int page = packet.getPageNumber(); + // Creative edits the NBT for us + if (session.getGameMode() != GameMode.CREATIVE) { + switch (packet.getAction()) { + case ADD_PAGE: { + // Add empty pages in between + for (int i = pages.size(); i < page; i++) { + pages.add(i, new StringTag("", "")); + } + pages.add(page, new StringTag("", packet.getText())); + break; + } + // Called whenever a page is modified + case REPLACE_PAGE: { + if (page < pages.size()) { + pages.set(page, new StringTag("", packet.getText())); + } else { + // Add empty pages in between + for (int i = pages.size(); i < page; i++) { + pages.add(i, new StringTag("", "")); + } + pages.add(page, new StringTag("", packet.getText())); + } + break; + } + case DELETE_PAGE: { + if (page < pages.size()) { + pages.remove(page); + } + break; + } + case SWAP_PAGES: { + int page2 = packet.getSecondaryPageNumber(); + if (page < pages.size() && page2 < pages.size()) { + Collections.swap(pages, page, page2); + } + break; + } + case SIGN_BOOK: { + tag.put(new StringTag("author", packet.getAuthor())); + tag.put(new StringTag("title", packet.getTitle())); + break; + } + default: + return; + } + } + // Remove empty pages at the end + while (pages.size() > 0) { + StringTag currentPage = (StringTag) pages.get(pages.size() - 1); + if (currentPage.getValue() == null || currentPage.getValue().isEmpty()) { + pages.remove(pages.size() - 1); + } else { + break; + } + } + tag.put(new ListTag("pages", pages)); + session.getInventory().setItem(36 + session.getInventory().getHeldItemSlot(), bookItem); + InventoryTranslator.INVENTORY_TRANSLATORS.get(null).updateInventory(session, session.getInventory()); + + session.getBookEditCache().setPacket(new ClientEditBookPacket(bookItem, packet.getAction() == BookEditPacket.Action.SIGN_BOOK, session.getInventory().getHeldItemSlot())); + // There won't be any more book updates after this, so we can try sending the edit packet immediately + if (packet.getAction() == BookEditPacket.Action.SIGN_BOOK) { + session.getBookEditCache().checkForSend(); + } + } + } +} 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 b756451bfea..228b2812f17 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 @@ -69,6 +69,9 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator Date: Thu, 7 Jan 2021 22:43:36 -0500 Subject: [PATCH 11/37] Block breaking refactors (#1336) Client-side block animations and reach checks are now added. This commit also includes cleanup in BlockChangeTranslator as well as proper Netherite tool support for calculating block breaking. Co-authored-by: rtm516 --- ...BedrockInventoryTransactionTranslator.java | 120 +++++++++++++++--- .../player/BedrockActionTranslator.java | 50 +++++++- .../entity/JavaEntityStatusTranslator.java | 15 ++- .../player/JavaPlayerActionAckTranslator.java | 97 +++++++------- .../java/world/JavaBlockChangeTranslator.java | 11 +- .../world/block/BlockTranslator.java | 4 +- .../geysermc/connector/utils/BlockUtils.java | 3 + 7 files changed, 223 insertions(+), 77 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 228b2812f17..8263507b207 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 @@ -39,12 +39,11 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlags; import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; -import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; -import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket; -import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.connector.entity.CommandBlockMinecartEntity; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; @@ -64,9 +63,18 @@ import java.util.concurrent.TimeUnit; +/** + * BedrockInventoryTransactionTranslator handles most interactions between the client and the world, + * or the client and their inventory. + */ @Translator(packet = InventoryTransactionPacket.class) public class BedrockInventoryTransactionTranslator extends PacketTranslator { + private static final float MAXIMUM_BLOCK_PLACING_DISTANCE = 64f; + private static final int CREATIVE_EYE_HEIGHT_PLACE_DISTANCE = 49; + private static final int SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE = 36; + private static final float MAXIMUM_BLOCK_DESTROYING_DISTANCE = 36f; + @Override public void translate(InventoryTransactionPacket packet, GeyserSession session) { // Send book updates before opening inventories @@ -112,6 +120,46 @@ public void translate(InventoryTransactionPacket packet, GeyserSession session) break; } + Vector3i blockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getBlockFace()); + /* + Checks to ensure that the range will be accepted by the server. + "Not in range" doesn't refer to how far a vanilla client goes (that's a whole other mess), + but how much a server will accept from the client maximum + */ + // CraftBukkit+ check - see https://github.com/PaperMC/Paper/blob/458db6206daae76327a64f4e2a17b67a7e38b426/Spigot-Server-Patches/0532-Move-range-check-for-block-placing-up.patch + Vector3f playerPosition = session.getPlayerEntity().getPosition(); + EntityFlags flags = session.getPlayerEntity().getMetadata().getFlags(); + + // Adjust position for current eye height + if (flags.getFlag(EntityFlag.SNEAKING)) { + playerPosition = playerPosition.sub(0, (EntityType.PLAYER.getOffset() - 1.27f), 0); + } else if (flags.getFlag(EntityFlag.SWIMMING) || flags.getFlag(EntityFlag.GLIDING) || flags.getFlag(EntityFlag.DAMAGE_NEARBY_MOBS)) { + // Swimming, gliding, or using the trident spin attack + playerPosition = playerPosition.sub(0, (EntityType.PLAYER.getOffset() - 0.4f), 0); + } else if (flags.getFlag(EntityFlag.SLEEPING)) { + playerPosition = playerPosition.sub(0, (EntityType.PLAYER.getOffset() - 0.2f), 0); + } // else, we don't have to modify the position + + float diffX = playerPosition.getX() - packet.getBlockPosition().getX(); + float diffY = playerPosition.getY() - packet.getBlockPosition().getY(); + float diffZ = playerPosition.getZ() - packet.getBlockPosition().getZ(); + if (((diffX * diffX) + (diffY * diffY) + (diffZ * diffZ)) > + (session.getGameMode().equals(GameMode.CREATIVE) ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) { + restoreCorrectBlock(session, blockPos, packet); + return; + } + + // Vanilla check + if (!(session.getPlayerEntity().getPosition().sub(0, EntityType.PLAYER.getOffset(), 0) + .distanceSquared(packet.getBlockPosition().toFloat().add(0.5f, 0.5f, 0.5f)) < MAXIMUM_BLOCK_PLACING_DISTANCE)) { + // The client thinks that its blocks have been successfully placed. Restore the server's blocks instead. + restoreCorrectBlock(session, blockPos, packet); + return; + } + /* + Block place checks end - client is good to go + */ + ClientPlayerPlaceBlockPacket blockPacket = new ClientPlayerPlaceBlockPacket( new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), BlockFace.values()[packet.getBlockFace()], @@ -159,7 +207,6 @@ else if (packet.getItemInHand() != null && ItemRegistry.BUCKETS.contains(packet. } } - Vector3i blockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getBlockFace()); ItemEntry handItem = ItemRegistry.getItem(packet.getItemInHand()); if (handItem.isBlock()) { session.setLastBlockPlacePosition(blockPos); @@ -184,19 +231,32 @@ else if (packet.getItemInHand() != null && ItemRegistry.BUCKETS.contains(packet. session.sendDownstreamPacket(useItemPacket); break; case 2: - int blockState = session.getConnector().getWorldManager().getBlockAt(session, packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()); - double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(blockState); - if (session.getGameMode() == GameMode.CREATIVE || (session.getConnector().getConfig().isCacheChunks() && blockHardness == 0)) { - session.setLastBlockPlacedId(null); - session.setLastBlockPlacePosition(null); + int blockState = session.getGameMode() == GameMode.CREATIVE ? + session.getConnector().getWorldManager().getBlockAt(session, packet.getBlockPosition()) : session.getBreakingBlock(); - LevelEventPacket blockBreakPacket = new LevelEventPacket(); - blockBreakPacket.setType(LevelEventType.PARTICLE_DESTROY_BLOCK); - blockBreakPacket.setPosition(packet.getBlockPosition().toFloat()); - blockBreakPacket.setData(BlockTranslator.getBedrockBlockId(blockState)); - session.sendUpstreamPacket(blockBreakPacket); + session.setLastBlockPlacedId(null); + session.setLastBlockPlacePosition(null); + + // Same deal with vanilla block placing as above. + // This is working out the distance using 3d Pythagoras and the extra value added to the Y is the sneaking height of a java player. + playerPosition = session.getPlayerEntity().getPosition(); + Vector3f floatBlockPosition = packet.getBlockPosition().toFloat(); + diffX = playerPosition.getX() - (floatBlockPosition.getX() + 0.5f); + diffY = (playerPosition.getY() - EntityType.PLAYER.getOffset()) - (floatBlockPosition.getY() + 0.5f) + 1.5f; + diffZ = playerPosition.getZ() - (floatBlockPosition.getZ() + 0.5f); + float distanceSquared = diffX * diffX + diffY * diffY + diffZ * diffZ; + if (distanceSquared > MAXIMUM_BLOCK_DESTROYING_DISTANCE) { + restoreCorrectBlock(session, packet.getBlockPosition(), packet); + return; } + LevelEventPacket blockBreakPacket = new LevelEventPacket(); + blockBreakPacket.setType(LevelEventType.PARTICLE_DESTROY_BLOCK); + blockBreakPacket.setPosition(packet.getBlockPosition().toFloat()); + blockBreakPacket.setData(BlockTranslator.getBedrockBlockId(blockState)); + session.sendUpstreamPacket(blockBreakPacket); + session.setBreakingBlock(BlockTranslator.JAVA_AIR_ID); + long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition()); if (frameEntityId != -1 && session.getEntityCache().getEntityByJavaId(frameEntityId) != null) { ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) frameEntityId, InteractAction.ATTACK, session.isSneaking()); @@ -270,4 +330,34 @@ else if (packet.getItemInHand() != null && ItemRegistry.BUCKETS.contains(packet. break; } } + + /** + * Restore the correct block state from the server without updating the chunk cache. + * + * @param session the session of the Bedrock client + * @param blockPos the block position to restore + */ + private void restoreCorrectBlock(GeyserSession session, Vector3i blockPos, InventoryTransactionPacket packet) { + int javaBlockState = session.getConnector().getWorldManager().getBlockAt(session, blockPos); + UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); + updateBlockPacket.setDataLayer(0); + updateBlockPacket.setBlockPosition(blockPos); + updateBlockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(javaBlockState)); + updateBlockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); + session.sendUpstreamPacket(updateBlockPacket); + + UpdateBlockPacket updateWaterPacket = new UpdateBlockPacket(); + updateWaterPacket.setDataLayer(1); + updateWaterPacket.setBlockPosition(blockPos); + updateWaterPacket.setRuntimeId(BlockTranslator.isWaterlogged(javaBlockState) ? BlockTranslator.BEDROCK_WATER_ID : BlockTranslator.BEDROCK_AIR_ID); + updateWaterPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); + session.sendUpstreamPacket(updateWaterPacket); + + // Reset the item in hand to prevent "missing" blocks + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.INVENTORY); + slotPacket.setSlot(packet.getHotbarSlot()); + slotPacket.setItem(packet.getItemInHand()); + session.sendUpstreamPacket(slotPacket); + } } 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 f6a6557ede8..c4dbbec405e 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 @@ -25,13 +25,16 @@ package org.geysermc.connector.network.translators.bedrock.entity.player; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; 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.entity.player.PlayerAction; import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState; import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerAbilitiesPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; @@ -40,9 +43,12 @@ import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket; import com.nukkitx.protocol.bedrock.packet.PlayerActionPacket; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.inventory.PlayerInventory; 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.world.block.BlockTranslator; import org.geysermc.connector.utils.BlockUtils; @@ -130,6 +136,27 @@ public void translate(PlayerActionPacket packet, GeyserSession session) { break; case START_BREAK: if (session.getConnector().getConfig().isCacheChunks()) { + // Start the block breaking animation + if (session.getGameMode() != GameMode.CREATIVE) { + int blockState = session.getConnector().getWorldManager().getBlockAt(session, vector); + double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(blockState); + LevelEventPacket startBreak = new LevelEventPacket(); + startBreak.setType(LevelEventType.BLOCK_START_BREAK); + startBreak.setPosition(vector.toFloat()); + PlayerInventory inventory = session.getInventory(); + ItemStack item = inventory.getItemInHand(); + ItemEntry itemEntry = null; + CompoundTag nbtData = new CompoundTag(""); + if (item != null) { + itemEntry = ItemRegistry.getItem(item); + nbtData = item.getNbt(); + } + double breakTime = Math.ceil(BlockUtils.getBreakTime(blockHardness, blockState, itemEntry, nbtData, session) * 20); + startBreak.setData((int) (65535 / breakTime)); + session.setBreakingBlock(blockState); + session.sendUpstreamPacket(startBreak); + } + // Account for fire - the client likes to hit the block behind. Vector3i fireBlockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getFace()); int blockUp = session.getConnector().getWorldManager().getBlockAt(session, fireBlockPos); @@ -138,24 +165,33 @@ public void translate(PlayerActionPacket packet, GeyserSession session) { ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(fireBlockPos.getX(), fireBlockPos.getY(), fireBlockPos.getZ()), BlockFace.values()[packet.getFace()]); session.sendDownstreamPacket(startBreakingPacket); - break; + if (session.getGameMode() == GameMode.CREATIVE) { + break; + } } } - ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(packet.getBlockPosition().getX(), - packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), BlockFace.values()[packet.getFace()]); + ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, position, BlockFace.values()[packet.getFace()]); session.sendDownstreamPacket(startBreakingPacket); break; case CONTINUE_BREAK: + if (session.getGameMode() == GameMode.CREATIVE) { + break; + } LevelEventPacket continueBreakPacket = new LevelEventPacket(); continueBreakPacket.setType(LevelEventType.PARTICLE_CRACK_BLOCK); - continueBreakPacket.setData(BlockTranslator.getBedrockBlockId(session.getBreakingBlock())); - continueBreakPacket.setPosition(packet.getBlockPosition().toFloat()); + continueBreakPacket.setData((BlockTranslator.getBedrockBlockId(session.getBreakingBlock())) | (packet.getFace() << 24)); + continueBreakPacket.setPosition(vector.toFloat()); session.sendUpstreamPacket(continueBreakPacket); break; case ABORT_BREAK: - ClientPlayerActionPacket abortBreakingPacket = new ClientPlayerActionPacket(PlayerAction.CANCEL_DIGGING, new Position(packet.getBlockPosition().getX(), - packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), BlockFace.DOWN); + ClientPlayerActionPacket abortBreakingPacket = new ClientPlayerActionPacket(PlayerAction.CANCEL_DIGGING, position, BlockFace.DOWN); session.sendDownstreamPacket(abortBreakingPacket); + LevelEventPacket stopBreak = new LevelEventPacket(); + stopBreak.setType(LevelEventType.BLOCK_STOP_BREAK); + stopBreak.setPosition(vector.toFloat()); + stopBreak.setData(0); + session.setBreakingBlock(BlockTranslator.JAVA_AIR_ID); + session.sendUpstreamPacket(stopBreak); break; case STOP_BREAK: // Handled in BedrockInventoryTransactionTranslator 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 1072826487f..59ea29925c7 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 @@ -31,8 +31,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.LevelSoundEvent2Packet; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet; import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; import org.geysermc.connector.entity.Entity; @@ -183,6 +183,19 @@ public void translate(ServerEntityStatusPacket packet, GeyserSession session) { return; } break; + case LIVING_EQUIPMENT_BREAK_HEAD: + case LIVING_EQUIPMENT_BREAK_CHEST: + case LIVING_EQUIPMENT_BREAK_LEGS: + case LIVING_EQUIPMENT_BREAK_FEET: + case LIVING_EQUIPMENT_BREAK_MAIN_HAND: + case LIVING_EQUIPMENT_BREAK_OFF_HAND: + LevelSoundEvent2Packet equipmentBreakPacket = new LevelSoundEvent2Packet(); + equipmentBreakPacket.setSound(SoundEvent.BREAK); + equipmentBreakPacket.setPosition(entity.getPosition()); + equipmentBreakPacket.setExtraData(-1); + equipmentBreakPacket.setIdentifier(""); + session.sendUpstreamPacket(equipmentBreakPacket); + return; } session.sendUpstreamPacket(entityEventPacket); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerActionAckTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerActionAckTranslator.java index d98f094a241..837adb126ff 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerActionAckTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerActionAckTranslator.java @@ -27,19 +27,20 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerActionAckPacket; -import com.github.steveice10.opennbt.tag.builtin.*; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import org.geysermc.connector.inventory.PlayerInventory; 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.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.utils.BlockUtils; -import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.ChunkUtils; @Translator(packet = ServerPlayerActionAckPacket.class) @@ -47,54 +48,54 @@ public class JavaPlayerActionAckTranslator extends PacketTranslator { @Override public void translate(ServerBlockChangePacket packet, GeyserSession session) { Position pos = packet.getRecord().getPosition(); - boolean updatePlacement = !(session.getConnector().getConfig().isCacheChunks() && session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()) == packet.getRecord().getBlock()); - ChunkUtils.updateBlock(session, packet.getRecord().getBlock(), packet.getRecord().getPosition()); - if (updatePlacement && session.getConnector().getPlatformType() != PlatformType.SPIGOT) { + boolean updatePlacement = session.getConnector().getPlatformType() != PlatformType.SPIGOT && // Spigot simply listens for the block place event + !(session.getConnector().getConfig().isCacheChunks() && + session.getConnector().getWorldManager().getBlockAt(session, pos) == packet.getRecord().getBlock()); + ChunkUtils.updateBlock(session, packet.getRecord().getBlock(), pos); + if (updatePlacement) { this.checkPlace(session, packet); } this.checkInteract(session, packet); 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 db6f43fea6b..b047999e7d1 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 @@ -87,7 +87,9 @@ public class BlockTranslator { */ public static final int BEDROCK_RUNTIME_COMMAND_BLOCK_ID; - // For block breaking animation math + /** + * A list of all Java runtime wool IDs, for use with block breaking math and shears + */ public static final IntSet JAVA_RUNTIME_WOOL_IDS = new IntOpenHashSet(); public static final int JAVA_RUNTIME_COBWEB_ID; diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java index 93909b20f92..c859b9f65fa 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java @@ -50,6 +50,7 @@ private static double toolBreakTimeBonus(String toolType, String toolTier, boole if (toolType.equals("shears")) return isWoolBlock ? 5.0 : 15.0; if (toolType.equals("")) return 1.0; switch (toolTier) { + // https://minecraft.gamepedia.com/Breaking#Speed case "wooden": return 2.0; case "stone": @@ -58,6 +59,8 @@ private static double toolBreakTimeBonus(String toolType, String toolTier, boole return 6.0; case "diamond": return 8.0; + case "netherite": + return 9.0; case "golden": return 12.0; default: From 9232c5565fb1d9ed6ebbb4cfddd0ed7562fdc7ba Mon Sep 17 00:00:00 2001 From: David Choo Date: Sat, 9 Jan 2021 20:43:03 -0500 Subject: [PATCH 12/37] Add Ender Dragon effects and sounds (#1781) * Add Ender Dragon effects and sounds * Add proper death effect and clean up * Add Ender Dragon respawn sound * Possibly fix dragon breath direction? * Update mappings * Fix death animation triggering at low health * Trigger death event when health is 0 and add explosions back * Add comment --- .../living/monster/EnderDragonEntity.java | 219 ++++++++++++++---- .../living/monster/EnderDragonPartEntity.java | 5 +- connector/src/main/resources/mappings | 2 +- 3 files changed, 184 insertions(+), 42 deletions(-) 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 df9456826a7..6216797982d 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 @@ -28,15 +28,26 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.AttributeData; +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.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; -import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import com.nukkitx.protocol.bedrock.packet.*; import lombok.Data; import org.geysermc.connector.entity.Tickable; +import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.entity.living.InsentientEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.AttributeUtils; +import org.geysermc.connector.utils.DimensionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicLong; public class EnderDragonEntity extends InsentientEntity implements Tickable { /** @@ -59,7 +70,19 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable { private final Segment[] segmentHistory = new Segment[19]; private int latestSegment = -1; - private boolean hovering; + private int phase; + /** + * The number of ticks since the beginning of the phase + */ + private int phaseTicks; + + private int ticksTillNextGrowl = 100; + + /** + * Used to determine when the wing flap sound should be played + */ + private float wingPosition; + private float lastWingPosition; public EnderDragonEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); @@ -69,49 +92,67 @@ public EnderDragonEntity(long entityId, long geyserId, EntityType entityType, Ve @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - // Phase - if (entityMetadata.getId() == 15) { - int value = (int) entityMetadata.getValue(); - if (value == 5) { - // Performing breath attack + if (entityMetadata.getId() == 15) { // Phase + phase = (int) entityMetadata.getValue(); + phaseTicks = 0; + metadata.getFlags().setFlag(EntityFlag.SITTING, isSitting()); + } + + super.updateBedrockMetadata(entityMetadata, session); + + if (entityMetadata.getId() == 8) { // Health + // Update the health attribute, so that the death animation gets played + // Round health up, so that Bedrock doesn't consider the dragon to be dead when health is between 0 and 1 + float health = (float) Math.ceil(metadata.getFloat(EntityData.HEALTH)); + if (phase == 9 && health <= 0) { // Dying phase EntityEventPacket entityEventPacket = new EntityEventPacket(); - entityEventPacket.setType(EntityEventType.DRAGON_FLAMING); + entityEventPacket.setType(EntityEventType.ENDER_DRAGON_DEATH); entityEventPacket.setRuntimeEntityId(geyserId); entityEventPacket.setData(0); session.sendUpstreamPacket(entityEventPacket); } - metadata.getFlags().setFlag(EntityFlag.SITTING, value == 5 || value == 6 || value == 7); - hovering = value == 10; + attributes.put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(health, 200)); + updateBedrockAttributes(session); } - super.updateBedrockMetadata(entityMetadata, session); + } + + /** + * Send an updated list of attributes to the Bedrock client. + * This is overwritten to allow the health attribute to differ from + * the health specified in the metadata. + * + * @param session GeyserSession + */ + @Override + public void updateBedrockAttributes(GeyserSession session) { + if (!valid) return; + + List attributes = new ArrayList<>(); + for (Map.Entry entry : this.attributes.entrySet()) { + if (!entry.getValue().getType().isBedrockAttribute()) + continue; + attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue())); + } + + UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); + updateAttributesPacket.setRuntimeEntityId(geyserId); + updateAttributesPacket.setAttributes(attributes); + session.sendUpstreamPacket(updateAttributesPacket); } @Override public void spawnEntity(GeyserSession session) { - AddEntityPacket addEntityPacket = new AddEntityPacket(); - addEntityPacket.setIdentifier("minecraft:" + entityType.name().toLowerCase()); - addEntityPacket.setRuntimeEntityId(geyserId); - addEntityPacket.setUniqueEntityId(geyserId); - addEntityPacket.setPosition(position); - addEntityPacket.setMotion(motion); - addEntityPacket.setRotation(getBedrockRotation()); - addEntityPacket.setEntityType(entityType.getType()); - addEntityPacket.getMetadata().putAll(metadata); - - // Otherwise dragon is always 'dying' - addEntityPacket.getAttributes().add(new AttributeData("minecraft:health", 0.0f, 200f, 200f, 200f)); - - 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); + super.spawnEntity(session); + + AtomicLong nextEntityId = session.getEntityCache().getNextEntityId(); + head = new EnderDragonPartEntity(entityId + 1, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 1, 1); + neck = new EnderDragonPartEntity(entityId + 2, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 3, 3); + body = new EnderDragonPartEntity(entityId + 3, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 5, 3); + leftWing = new EnderDragonPartEntity(entityId + 4, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 4, 2); + rightWing = new EnderDragonPartEntity(entityId + 5, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 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); + tail[i] = new EnderDragonPartEntity(entityId + 6 + i, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 2, 2); } allParts = new EnderDragonPartEntity[]{head, neck, body, leftWing, rightWing, tail[0], tail[1], tail[2]}; @@ -125,8 +166,6 @@ public void spawnEntity(GeyserSession session) { segmentHistory[i].yaw = rotation.getZ(); segmentHistory[i].y = position.getY(); } - - session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); } @Override @@ -139,8 +178,11 @@ public boolean despawnEntity(GeyserSession session) { @Override public void tick(GeyserSession session) { - pushSegment(); - updateBoundingBoxes(session); + effectTick(session); + if (!metadata.getFlags().getFlag(EntityFlag.NO_AI) && isAlive()) { + pushSegment(); + updateBoundingBoxes(session); + } } /** @@ -158,7 +200,7 @@ private void updateBoundingBoxes(GeyserSession session) { // Lowers the head when the dragon sits/hovers float headDuck; - if (hovering || metadata.getFlags().getFlag(EntityFlag.SITTING)) { + if (isHovering() || isSitting()) { headDuck = -1f; } else { headDuck = baseSegment.y - getSegment(0).y; @@ -188,6 +230,105 @@ private void updateBoundingBoxes(GeyserSession session) { } } + /** + * Handles the particles and sounds of the Ender Dragon + * @param session GeyserSession. + */ + private void effectTick(GeyserSession session) { + Random random = ThreadLocalRandom.current(); + if (!metadata.getFlags().getFlag(EntityFlag.SILENT)) { + if (Math.cos(wingPosition * 2f * Math.PI) <= -0.3f && Math.cos(lastWingPosition * 2f * Math.PI) >= -0.3f) { + PlaySoundPacket playSoundPacket = new PlaySoundPacket(); + playSoundPacket.setSound("mob.enderdragon.flap"); + playSoundPacket.setPosition(position); + playSoundPacket.setVolume(5f); + playSoundPacket.setPitch(0.8f + random.nextFloat() * 0.3f); + session.sendUpstreamPacket(playSoundPacket); + } + + if (!isSitting() && !isHovering() && ticksTillNextGrowl-- == 0) { + playGrowlSound(session); + ticksTillNextGrowl = 200 + random.nextInt(200); + } + + lastWingPosition = wingPosition; + } + if (isAlive()) { + if (metadata.getFlags().getFlag(EntityFlag.NO_AI)) { + wingPosition = 0.5f; + } else if (isHovering() || isSitting()) { + wingPosition += 0.1f; + } else { + double speed = motion.length(); + wingPosition += 0.2f / (speed * 10f + 1) * Math.pow(2, motion.getY()); + } + + phaseTicks++; + if (phase == 3) { // Landing Phase + float headHeight = head.getMetadata().getFloat(EntityData.BOUNDING_BOX_HEIGHT); + Vector3f headCenter = head.getPosition().up(headHeight * 0.5f); + + for (int i = 0; i < 8; i++) { + Vector3f particlePos = headCenter.add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f); + // This is missing velocity information + LevelEventPacket particlePacket = new LevelEventPacket(); + particlePacket.setType(LevelEventType.PARTICLE_DRAGONS_BREATH); + particlePacket.setPosition(particlePos); + session.sendUpstreamPacket(particlePacket); + } + } else if (phase == 5) { // Sitting Flaming Phase + if (phaseTicks % 2 == 0 && phaseTicks < 10) { + // Performing breath attack + // Entity event DRAGON_FLAMING seems to create particles from the origin of the dragon, + // so we need to manually spawn particles + for (int i = 0; i < 8; i++) { + SpawnParticleEffectPacket spawnParticleEffectPacket = new SpawnParticleEffectPacket(); + spawnParticleEffectPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension())); + spawnParticleEffectPacket.setPosition(head.getPosition().add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f)); + spawnParticleEffectPacket.setIdentifier("minecraft:dragon_breath_fire"); + session.sendUpstreamPacket(spawnParticleEffectPacket); + } + } + } else if (phase == 7) { // Sitting Attacking Phase + playGrowlSound(session); + } else if (phase == 9) { // Dying Phase + // Send explosion particles as the dragon move towards the end portal + if (phaseTicks % 10 == 0) { + float xOffset = 8f * (random.nextFloat() - 0.5f); + float yOffset = 4f * (random.nextFloat() - 0.5f) + 2f; + float zOffset = 8f * (random.nextFloat() - 0.5f); + Vector3f particlePos = position.add(xOffset, yOffset, zOffset); + LevelEventPacket particlePacket = new LevelEventPacket(); + particlePacket.setType(LevelEventType.PARTICLE_EXPLOSION); + particlePacket.setPosition(particlePos); + session.sendUpstreamPacket(particlePacket); + } + } + } + } + + private void playGrowlSound(GeyserSession session) { + Random random = ThreadLocalRandom.current(); + PlaySoundPacket playSoundPacket = new PlaySoundPacket(); + playSoundPacket.setSound("mob.enderdragon.growl"); + playSoundPacket.setPosition(position); + playSoundPacket.setVolume(2.5f); + playSoundPacket.setPitch(0.8f + random.nextFloat() * 0.3f); + session.sendUpstreamPacket(playSoundPacket); + } + + private boolean isAlive() { + return metadata.getFloat(EntityData.HEALTH) > 0; + } + + private boolean isHovering() { + return phase == 10; + } + + private boolean isSitting() { + return phase == 5 || phase == 6 || phase == 7; + } + /** * Store the current yaw and y into the circular buffer */ 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 index 095d12b2cc9..288a3e4238b 100644 --- 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 @@ -32,11 +32,12 @@ 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); + public EnderDragonPartEntity(long entityId, long geyserId, EntityType entityType, float width, float height) { + super(entityId, geyserId, entityType, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO); metadata.put(EntityData.BOUNDING_BOX_WIDTH, width); metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height); metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true); + metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true); } } diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 143285afb4b..dd0347bd51e 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 143285afb4bdf4d5ef40ef7a7959477dabf4d34c +Subproject commit dd0347bd51e00e42ea58faaf68b562526c4d2817 From d6461e71fb1e6e804b3e674f7e6f741810ad3864 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 10 Jan 2021 20:00:48 -0500 Subject: [PATCH 13/37] Add message if Geyser thinks the English locale is missing (#1825) --- .../java/org/geysermc/connector/utils/LanguageUtils.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 c6ab715d483..1a1f758d69d 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java @@ -187,7 +187,11 @@ private static boolean isValidLanguage(String locale) { if (FileUtils.class.getResource("/languages/texts/" + locale + ".properties") == null) { result = false; if (GeyserConnector.getInstance() != null && GeyserConnector.getInstance().getLogger() != null) { // Could be too early for these to be initialized - GeyserConnector.getInstance().getLogger().warning(locale + " is not a valid Bedrock language."); // We can't translate this since we just loaded an invalid language + if (locale.equals("en_US")) { + GeyserConnector.getInstance().getLogger().error("English locale not found in Geyser. Did you clone the submodules? (git submodule update --init)"); + } else { + GeyserConnector.getInstance().getLogger().warning(locale + " is not a valid Bedrock language."); // We can't translate this since we just loaded an invalid language + } } } else { if (!LOCALE_MAPPINGS.containsKey(locale)) { From 999fa7298a0e67bbe4b32297f06246485f025191 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 10 Jan 2021 20:01:04 -0500 Subject: [PATCH 14/37] Clarify proxy protocol config message (#1824) --- connector/src/main/resources/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 42e004db148..2288527859f 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -35,7 +35,8 @@ remote: # Whether to enable PROXY protocol or not while connecting to the server. # This is useful only when: # 1) Your server supports PROXY protocol (it probably doesn't) - # 2) You run Velocity or BungeeCord with respective option enabled. + # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. + # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! use-proxy-protocol: false # Floodgate uses encryption to ensure use from authorised sources. From 2cc8726c1256ad33d6f17569ba067e49314380e3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 11 Jan 2021 14:11:21 -0500 Subject: [PATCH 15/37] Shade in the entire net.kyori package (#1826) --- bootstrap/bungeecord/pom.xml | 4 ++-- bootstrap/spigot/pom.xml | 4 ++-- bootstrap/sponge/pom.xml | 4 ++-- bootstrap/velocity/pom.xml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 124967b0ae1..54e0d56ef21 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -86,8 +86,8 @@ org.geysermc.platform.bungeecord.shaded.dom4j - net.kyori.adventure - org.geysermc.platform.bungeecord.shaded.adventure + net.kyori + org.geysermc.platform.bungeecord.shaded.kyori diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index adaa7557f7f..93eebc3d2c4 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -97,8 +97,8 @@ org.geysermc.platform.spigot.shaded.dom4j - net.kyori.adventure - org.geysermc.platform.spigot.shaded.adventure + net.kyori + org.geysermc.platform.spigot.shaded.kyori diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index e6ce8f85134..97c4ac8a4b7 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -86,8 +86,8 @@ org.geysermc.platform.sponge.shaded.dom4j - net.kyori.adventure - org.geysermc.platform.sponge.shaded.adventure + net.kyori + org.geysermc.platform.sponge.shaded.kyori diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 2fedca71a61..5c0824def5e 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -82,8 +82,8 @@ org.geysermc.platform.velocity.shaded.dom4j - net.kyori.adventure - org.geysermc.platform.velocity.shaded.adventure + net.kyori + org.geysermc.platform.velocity.shaded.kyori From 6aa74a2322f0555007585191c2a085b19e3b1efb Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 11 Jan 2021 15:37:37 -0500 Subject: [PATCH 16/37] Allow server pong to appear if MOTDs are too long (#1445) * Prevent server pong from appearing if MOTDs are too long If the server MOTDs are 340 characters or longer, they will not appear. If this is the case, we trim each. * Implement a more exact fix --- .../network/ConnectorServerEventHandler.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java index 79c04f674f7..87883087dfb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java @@ -30,15 +30,16 @@ import com.nukkitx.protocol.bedrock.BedrockServerSession; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.socket.DatagramPacket; -import org.geysermc.connector.common.ping.GeyserPingInfo; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.common.ping.GeyserPingInfo; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.network.translators.chat.MessageTranslator; +import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.utils.LanguageUtils; import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; public class ConnectorServerEventHandler implements BedrockServerEventHandler { @@ -94,6 +95,20 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { pong.setMaximumPlayerCount(config.getMaxPlayers()); } + // The ping will not appear if the MOTD + sub-MOTD is of a certain length. + // We don't know why, though + byte[] motdArray = pong.getMotd().getBytes(StandardCharsets.UTF_8); + if (motdArray.length + pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length > 338) { + // Remove the sub-MOTD first since that only appears locally + pong.setSubMotd(""); + if (motdArray.length > 338) { + // If the top MOTD is still too long, we chop it down + byte[] newMotdArray = new byte[339]; + System.arraycopy(motdArray, 0, newMotdArray, 0, newMotdArray.length); + pong.setMotd(new String(newMotdArray, StandardCharsets.UTF_8)); + } + } + //Bedrock will not even attempt a connection if the client thinks the server is full //so we have to fake it not being full if (pong.getPlayerCount() >= pong.getMaximumPlayerCount()) { From 1c0cc4622a04df9f3c3bdccac4c3a600cda0b94e Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 11 Jan 2021 20:52:02 +0000 Subject: [PATCH 17/37] Microsoft account authentication (#1808) Microsoft accounts can now use Geyser, while maintaining full backwards compatibility with Mojang accounts. Co-authored-by: Camotoy <20743703+Camotoy@users.noreply.github.com> --- connector/pom.xml | 9 + .../geysermc/connector/GeyserConnector.java | 9 +- .../configuration/GeyserConfiguration.java | 8 + .../GeyserJacksonConfiguration.java | 7 + .../network/UpstreamPacketHandler.java | 1 + .../network/session/GeyserSession.java | 333 +++++++++++------- .../JavaPlayerPositionRotationTranslator.java | 2 +- .../java/world/JavaUpdateTimeTranslator.java | 11 +- .../connector/utils/LoginEncryptionUtils.java | 80 ++++- connector/src/main/resources/config.yml | 5 + connector/src/main/resources/languages | 2 +- 11 files changed, 326 insertions(+), 141 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index de095a33bcb..a7001be0062 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -132,6 +132,10 @@ com.github.steveice10 packetlib + + com.github.steveice10 + mcauthlib + @@ -198,6 +202,11 @@ 4.13.1 test + + com.github.GeyserMC + MCAuthLib + 0e48a094f2 + diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 5dd00dabe78..d61500e0455 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -86,6 +86,11 @@ public class GeyserConnector { public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs public static final String VERSION = "DEV"; // A fallback for running in IDEs + /** + * Oauth client ID for Microsoft authentication + */ + public static final String OAUTH_CLIENT_ID = "204cefd1-4818-4de1-b98d-513fae875d88"; + private static final String IP_REGEX = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b"; private final List players = new ArrayList<>(); @@ -101,8 +106,8 @@ public class GeyserConnector { private final ScheduledExecutorService generalThreadPool; private BedrockServer bedrockServer; - private PlatformType platformType; - private GeyserBootstrap bootstrap; + private final PlatformType platformType; + private final GeyserBootstrap bootstrap; private Metrics metrics; diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java index d21893f89ec..e21aa6bb814 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -118,6 +118,8 @@ interface IRemoteConfiguration { String getAuthType(); + boolean isPasswordAuthentication(); + boolean isUseProxyProtocol(); } @@ -125,6 +127,12 @@ interface IUserAuthenticationInfo { String getEmail(); String getPassword(); + + /** + * Will be removed after Microsoft accounts are fully migrated + */ + @Deprecated + boolean isMicrosoftAccount(); } interface IMetricsInfo { 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 3a8946e00fb..7c9532ff849 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -149,17 +149,24 @@ public static class RemoteConfiguration implements IRemoteConfiguration { @JsonProperty("auth-type") private String authType = "online"; + @JsonProperty("allow-password-authentication") + private boolean passwordAuthentication = true; + @JsonProperty("use-proxy-protocol") private boolean useProxyProtocol = false; } @Getter + @JsonIgnoreProperties(ignoreUnknown = true) // DO NOT REMOVE THIS! Otherwise, after we remove microsoft-account configs will not load public static class UserAuthenticationInfo implements IUserAuthenticationInfo { @AsteriskSerializer.Asterisk() private String email; @AsteriskSerializer.Asterisk() private String password; + + @JsonProperty("microsoft-account") + private boolean microsoftAccount = false; } @Getter 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 a6a369e450b..3922a95ffc6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -161,6 +161,7 @@ private boolean couldLoginUserByName(String bedrockUsername) { if (info != null) { connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().getName())); + session.setMicrosoftAccount(info.isMicrosoftAccount()); session.authenticate(info.getEmail(), info.getPassword()); // TODO send a message to bedrock user telling them they are connected (if nothing like a motd 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 8a0362663e5..a82ea061e5a 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 @@ -26,8 +26,12 @@ package org.geysermc.connector.network.session; import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.auth.exception.request.AuthPendingException; import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException; import com.github.steveice10.mc.auth.exception.request.RequestException; +import com.github.steveice10.mc.auth.service.AuthenticationService; +import com.github.steveice10.mc.auth.service.MojangAuthenticationService; +import com.github.steveice10.mc.auth.service.MsaAuthenticationService; import com.github.steveice10.mc.protocol.MinecraftConstants; import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.SubProtocol; @@ -110,6 +114,10 @@ public class GeyserSession implements CommandSender { @Setter private BedrockClientData clientData; + @Deprecated + @Setter + private boolean microsoftAccount; + private final SessionPlayerEntity playerEntity; private PlayerInventory inventory; @@ -257,7 +265,6 @@ public class GeyserSession implements CommandSender { /** * Controls whether the daylight cycle gamerule has been sent to the client, so the sun/moon remain motionless. */ - @Setter private boolean daylightCycle = true; private boolean reducedDebugInfo = false; @@ -443,146 +450,222 @@ public void authenticate(String username, String password) { new Thread(() -> { try { if (password != null && !password.isEmpty()) { - protocol = new MinecraftProtocol(username, password); + AuthenticationService authenticationService; + if (microsoftAccount) { + authenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID); + } else { + authenticationService = new MojangAuthenticationService(); + } + authenticationService.setUsername(username); + authenticationService.setPassword(password); + authenticationService.login(); + + protocol = new MinecraftProtocol(authenticationService); } else { protocol = new MinecraftProtocol(username); } - boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE; - final PublicKey publicKey; + connectDownstream(); + } catch (InvalidCredentialsException | IllegalArgumentException e) { + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", username)); + disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode())); + } catch (RequestException ex) { + ex.printStackTrace(); + } + }).start(); + } + + /** + * Present a form window to the user asking to log in with another web browser + */ + public void authenticateWithMicrosoftCode() { + if (loggedIn) { + connector.getLogger().severe(LanguageUtils.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().getName())); + return; + } + + loggingIn = true; + // new thread so clients don't timeout + new Thread(() -> { + try { + MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID); + + MsaAuthenticationService.MsCodeResponse response = msaAuthenticationService.getAuthCode(); + LoginEncryptionUtils.showMicrosoftCodeWindow(this, response); + + // This just looks cool + SetTimePacket packet = new SetTimePacket(); + packet.setTime(16000); + sendUpstreamPacket(packet); + + // Wait for the code to validate + attemptCodeAuthentication(msaAuthenticationService); + } catch (InvalidCredentialsException | IllegalArgumentException e) { + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", getAuthData().getName())); + disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode())); + } catch (RequestException ex) { + ex.printStackTrace(); + } + }).start(); + } + + /** + * Poll every second to see if the user has successfully signed in + */ + private void attemptCodeAuthentication(MsaAuthenticationService msaAuthenticationService) { + if (loggedIn || closed) { + return; + } + try { + msaAuthenticationService.login(); + protocol = new MinecraftProtocol(msaAuthenticationService); + + connectDownstream(); + } catch (RequestException e) { + if (!(e instanceof AuthPendingException)) { + e.printStackTrace(); + } else { + // Wait one second before trying again + connector.getGeneralThreadPool().schedule(() -> attemptCodeAuthentication(msaAuthenticationService), 1, TimeUnit.SECONDS); + } + } + } + + /** + * After getting whatever credentials needed, we attempt to join the Java server. + */ + private void connectDownstream() { + boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE; + final PublicKey publicKey; - if (floodgate) { - PublicKey key = null; + if (floodgate) { + PublicKey key = null; + try { + key = EncryptionUtil.getKeyFromFile( + connector.getConfig().getFloodgateKeyPath(), + PublicKey.class + ); + } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) { + connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e); + } + publicKey = key; + } else publicKey = null; + + if (publicKey != null) { + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); + } + + // Start ticking + tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS); + + downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory()); + if (connector.getConfig().getRemote().isUseProxyProtocol()) { + downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true); + downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress()); + } + // 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) { + //todo move this somewhere else + if (event.getPacket() instanceof HandshakePacket && floodgate) { + String encrypted = ""; try { - key = EncryptionUtil.getKeyFromFile( - connector.getConfig().getFloodgateKeyPath(), - PublicKey.class - ); - } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) { - connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e); + encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData( + clientData.getGameVersion(), + authData.getName(), + authData.getXboxUUID(), + clientData.getDeviceOS().ordinal(), + clientData.getLanguageCode(), + clientData.getCurrentInputMode().ordinal(), + upstream.getSession().getAddress().getAddress().getHostAddress() + )); + } catch (Exception e) { + connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); } - publicKey = key; - } else publicKey = null; - if (publicKey != null) { - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); + HandshakePacket handshakePacket = event.getPacket(); + event.setPacket(new HandshakePacket( + handshakePacket.getProtocolVersion(), + handshakePacket.getHostname() + '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted, + handshakePacket.getPort(), + handshakePacket.getIntent() + )); } + } - // Start ticking - tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS); - - downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory()); - if (connector.getConfig().getRemote().isUseProxyProtocol()) { - downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true); - downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress()); + @Override + public void connected(ConnectedEvent event) { + loggingIn = false; + loggedIn = true; + if (protocol.getProfile() == null) { + // Java account is offline + disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode())); + return; } - // 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) { - //todo move this somewhere else - if (event.getPacket() instanceof HandshakePacket && floodgate) { - String encrypted = ""; - try { - encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData( - clientData.getGameVersion(), - authData.getName(), - authData.getXboxUUID(), - clientData.getDeviceOS().ordinal(), - clientData.getLanguageCode(), - clientData.getCurrentInputMode().ordinal(), - upstream.getSession().getAddress().getAddress().getHostAddress() - )); - } catch (Exception e) { - connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); - } - - HandshakePacket handshakePacket = event.getPacket(); - event.setPacket(new HandshakePacket( - handshakePacket.getProtocolVersion(), - handshakePacket.getHostname() + '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted, - handshakePacket.getPort(), - handshakePacket.getIntent() - )); - } - } - - @Override - public void connected(ConnectedEvent event) { - loggingIn = false; - loggedIn = true; - if (protocol.getProfile() == null) { - // Java account is offline - disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode())); - return; - } - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.connect", authData.getName(), protocol.getProfile().getName(), remoteServer.getAddress())); - playerEntity.setUuid(protocol.getProfile().getId()); - playerEntity.setUsername(protocol.getProfile().getName()); + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.connect", authData.getName(), protocol.getProfile().getName(), remoteServer.getAddress())); + playerEntity.setUuid(protocol.getProfile().getId()); + playerEntity.setUsername(protocol.getProfile().getName()); - String locale = clientData.getLanguageCode(); + String locale = clientData.getLanguageCode(); - // Let the user know there locale may take some time to download - // 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("Loading your locale (en_us); if this isn't already downloaded, this may take some time"); - } + // Let the user know there locale may take some time to download + // 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("Loading your locale (en_us); if this isn't already downloaded, this may take some time"); + } - // Download and load the language for the player - LocaleUtils.downloadAndLoadLocale(locale); - } + // Download and load the language for the player + LocaleUtils.downloadAndLoadLocale(locale); + } - @Override - public void disconnected(DisconnectedEvent event) { - loggingIn = false; - loggedIn = false; - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.disconnect", authData.getName(), remoteServer.getAddress(), event.getReason())); - if (event.getCause() != null) { - event.getCause().printStackTrace(); - } + @Override + public void disconnected(DisconnectedEvent event) { + loggingIn = false; + loggedIn = false; + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.disconnect", authData.getName(), remoteServer.getAddress(), event.getReason())); + if (event.getCause() != null) { + event.getCause().printStackTrace(); + } - upstream.disconnect(MessageTranslator.convertMessageLenient(event.getReason())); - } + upstream.disconnect(MessageTranslator.convertMessageLenient(event.getReason())); + } - @Override - public void packetReceived(PacketReceivedEvent event) { - if (!closed) { - // Required, or else Floodgate players break with Bukkit chunk caching - if (event.getPacket() instanceof LoginSuccessPacket) { - GameProfile profile = ((LoginSuccessPacket) event.getPacket()).getProfile(); - playerEntity.setUsername(profile.getName()); - playerEntity.setUuid(profile.getId()); - - // Check if they are not using a linked account - if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) { - SkinManager.handleBedrockSkin(playerEntity, clientData); - } - } - - PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this); + @Override + public void packetReceived(PacketReceivedEvent event) { + if (!closed) { + // Required, or else Floodgate players break with Bukkit chunk caching + if (event.getPacket() instanceof LoginSuccessPacket) { + GameProfile profile = ((LoginSuccessPacket) event.getPacket()).getProfile(); + playerEntity.setUsername(profile.getName()); + playerEntity.setUuid(profile.getId()); + + // Check if they are not using a linked account + if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) { + SkinManager.handleBedrockSkin(playerEntity, clientData); } } - @Override - public void packetError(PacketErrorEvent event) { - connector.getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.downstream_error", event.getCause().getMessage())); - if (connector.getConfig().isDebugMode()) - event.getCause().printStackTrace(); - event.setSuppress(true); - } - }); + PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this); + } + } - downstream.getSession().connect(); - connector.addPlayer(this); - } catch (InvalidCredentialsException | IllegalArgumentException e) { - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", username)); - disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode())); - } catch (RequestException ex) { - ex.printStackTrace(); + @Override + public void packetError(PacketErrorEvent event) { + connector.getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.downstream_error", event.getCause().getMessage())); + if (connector.getConfig().isDebugMode()) + event.getCause().printStackTrace(); + event.setSuppress(true); } - }).start(); + }); + + if (!daylightCycle) { + setDaylightCycle(true); + } + downstream.getSession().connect(); + connector.addPlayer(this); } public void disconnect(String reason) { @@ -872,6 +955,18 @@ public void setReducedDebugInfo(boolean value) { reducedDebugInfo = value; } + /** + * Changes the daylight cycle gamerule on the client + * This is used in the login screen along-side normal usage + * + * @param doCycle If the cycle should continue + */ + public void setDaylightCycle(boolean doCycle) { + sendGameRule("dodaylightcycle", doCycle); + // Save the value so we don't have to constantly send a daylight cycle gamerule update + this.daylightCycle = doCycle; + } + /** * Send a gamerule value to the client * diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java index d5d8e7d3c34..b379443e3d5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java @@ -81,7 +81,7 @@ public void translate(ServerPlayerPositionRotationPacket packet, GeyserSession s ChunkUtils.updateChunkPosition(session, pos.toInt()); - session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.entity.player.spawn", packet.getX(), packet.getY(), packet.getZ())); + session.getConnector().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.entity.player.spawn", packet.getX(), packet.getY(), packet.getZ())); return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java index dd1ec68a326..461d8139d15 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java @@ -46,17 +46,10 @@ public void translate(ServerUpdateTimePacket packet, GeyserSession session) { session.sendUpstreamPacket(setTimePacket); if (!session.isDaylightCycle() && time >= 0) { // Client thinks there is no daylight cycle but there is - setDoDaylightCycleGamerule(session, true); + session.setDaylightCycle(true); } else if (session.isDaylightCycle() && time < 0) { // Client thinks there is daylight cycle but there isn't - setDoDaylightCycleGamerule(session, false); + session.setDaylightCycle(false); } } - - private void setDoDaylightCycleGamerule(GeyserSession session, boolean doCycle) { - session.sendGameRule("dodaylightcycle", doCycle); - // Save the value so we don't have to constantly send a daylight cycle gamerule update - session.setDaylightCycle(doCycle); - } - } 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 5c212ba02c0..fd7ef4e6405 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -29,19 +29,18 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeType; +import com.github.steveice10.mc.auth.service.MsaAuthenticationService; import com.nimbusds.jose.JWSObject; import com.nukkitx.network.util.Preconditions; import com.nukkitx.protocol.bedrock.packet.LoginPacket; import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket; import com.nukkitx.protocol.bedrock.util.EncryptionUtils; -import org.geysermc.common.window.CustomFormBuilder; -import org.geysermc.common.window.CustomFormWindow; -import org.geysermc.common.window.FormWindow; -import org.geysermc.common.window.SimpleFormWindow; +import org.geysermc.common.window.*; import org.geysermc.common.window.button.FormButton; import org.geysermc.common.window.component.InputComponent; import org.geysermc.common.window.component.LabelComponent; import org.geysermc.common.window.response.CustomFormResponse; +import org.geysermc.common.window.response.ModalFormResponse; import org.geysermc.common.window.response.SimpleFormResponse; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; @@ -156,13 +155,21 @@ private static void startEncryptionHandshake(GeyserSession session, PublicKey ke session.sendUpstreamPacketImmediately(packet); } - private static int AUTH_FORM_ID = 1336; - private static int AUTH_DETAILS_FORM_ID = 1337; + private static final int AUTH_MSA_DETAILS_FORM_ID = 1334; + private static final int AUTH_MSA_CODE_FORM_ID = 1335; + private static final int AUTH_FORM_ID = 1336; + private static final int AUTH_DETAILS_FORM_ID = 1337; public static void showLoginWindow(GeyserSession session) { + // Set DoDaylightCycle to false so the time doesn't accelerate while we're here + session.setDaylightCycle(false); + 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))); + if (session.getConnector().getConfig().getRemote().isPasswordAuthentication()) { + window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.mojang", userLanguage))); + } + window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.microsoft", userLanguage))); window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage))); session.sendForm(window, AUTH_FORM_ID); @@ -179,12 +186,33 @@ public static void showLoginDetailsWindow(GeyserSession session) { session.sendForm(window, AUTH_DETAILS_FORM_ID); } + /** + * Prompts the user between either OAuth code login or manual password authentication + */ + public static void showMicrosoftAuthenticationWindow(GeyserSession session) { + String userLanguage = session.getLocale(); + SimpleFormWindow window = new SimpleFormWindow(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.microsoft", userLanguage), ""); + window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.method.browser", userLanguage))); + window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.method.password", userLanguage))); // This form won't show if password authentication is disabled + window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage))); + session.sendForm(window, AUTH_MSA_DETAILS_FORM_ID); + } + + /** + * Shows the code that a user must input into their browser + */ + public static void showMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse response) { + ModalFormWindow msaCodeWindow = new ModalFormWindow("%xbox.signin", "%xbox.signin.website\n%xbox.signin.url\n%xbox.signin.enterCode\n" + + response.user_code, "Done", "%menu.disconnect"); + session.sendForm(msaCodeWindow, LoginEncryptionUtils.AUTH_MSA_CODE_FORM_ID); + } + public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, int formId, String formData) { WindowCache windowCache = session.getWindowCache(); if (!windowCache.getWindows().containsKey(formId)) return false; - if(formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID) { + if (formId == AUTH_MSA_DETAILS_FORM_ID || formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID || formId == AUTH_MSA_CODE_FORM_ID) { FormWindow window = windowCache.getWindows().remove(formId); window.setResponse(formData.trim()); @@ -205,16 +233,50 @@ public static boolean authenticateFromForm(GeyserSession session, GeyserConnecto showLoginDetailsWindow(session); } } else if (formId == AUTH_FORM_ID && window instanceof SimpleFormWindow) { + boolean isPasswordAuthentication = session.getConnector().getConfig().getRemote().isPasswordAuthentication(); + int microsoftButton = isPasswordAuthentication ? 1 : 0; + int disconnectButton = isPasswordAuthentication ? 2 : 1; + SimpleFormResponse response = (SimpleFormResponse) window.getResponse(); + if (response != null) { + if (isPasswordAuthentication && response.getClickedButtonId() == 0) { + session.setMicrosoftAccount(false); + showLoginDetailsWindow(session); + } else if (response.getClickedButtonId() == microsoftButton) { + session.setMicrosoftAccount(true); + if (isPasswordAuthentication) { + showMicrosoftAuthenticationWindow(session); + } else { + // Just show the OAuth code + session.authenticateWithMicrosoftCode(); + } + } else if (response.getClickedButtonId() == disconnectButton) { + session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); + } + } else { + showLoginWindow(session); + } + } else if (formId == AUTH_MSA_DETAILS_FORM_ID && window instanceof SimpleFormWindow) { SimpleFormResponse response = (SimpleFormResponse) window.getResponse(); if (response != null) { if (response.getClickedButtonId() == 0) { + session.authenticateWithMicrosoftCode(); + } else if (response.getClickedButtonId() == 1) { showLoginDetailsWindow(session); - } else if(response.getClickedButtonId() == 1) { + } else if (response.getClickedButtonId() == 2) { session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); } } else { showLoginWindow(session); } + } else if (formId == AUTH_MSA_CODE_FORM_ID && window instanceof ModalFormWindow) { + ModalFormResponse response = (ModalFormResponse) window.getResponse(); + if (response != null) { + if (response.getClickedButtonId() == 1) { + session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); + } + } else { + showMicrosoftAuthenticationWindow(session); + } } } } diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 2288527859f..07b73173e5b 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -32,6 +32,9 @@ remote: port: 25565 # Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate). auth-type: online + # Allow for password-based authentication methods through Geyser. Only useful in online mode. + # If this is false, users must authenticate to Microsoft using a code provided by Geyser on their desktop. + allow-password-authentication: true # Whether to enable PROXY protocol or not while connecting to the server. # This is useful only when: # 1) Your server supports PROXY protocol (it probably doesn't) @@ -52,10 +55,12 @@ floodgate-key-file: public-key.pem # BedrockAccountUsername: # Your Minecraft: Bedrock Edition username # email: javaccountemail@example.com # Your Minecraft: Java Edition email # password: javaccountpassword123 # Your Minecraft: Java Edition password +# microsoft-account: true # Whether the account is a Mojang or Microsoft account. # # bluerkelp2: # email: not_really_my_email_address_mr_minecrafter53267@gmail.com # password: "this isn't really my password" +# microsoft-account: false # 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. diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index 1a00766840b..6f246c24ddb 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 1a00766840baf1f512d98f5a75c177c8bcfba6f3 +Subproject commit 6f246c24ddbd543a359d651e706da470fe53ceeb From fb283fcce8600eca01c777d0df94217e978f1413 Mon Sep 17 00:00:00 2001 From: YHDiamond <47502993+YHDiamond@users.noreply.github.com> Date: Mon, 11 Jan 2021 21:23:09 -0500 Subject: [PATCH 18/37] Add advancements GUI (#1579) Using /geyser advancements, Bedrock clients can get a visual on their progress. Co-authored-by: yehudahrrs <47502993+yehudahrrs@users.noreply.github.com> Co-authored-by: Olivia Co-authored-by: rtm516 Co-authored-by: DoctorMacc Co-authored-by: rtm516 Co-authored-by: Camotoy <20743703+Camotoy@users.noreply.github.com> --- .../connector/command/CommandManager.java | 1 + .../command/defaults/AdvancementsCommand.java | 74 ++++ .../network/UpstreamPacketHandler.java | 28 +- .../network/session/GeyserSession.java | 3 + .../session/cache/AdvancementsCache.java | 321 ++++++++++++++++++ .../network/session/cache/WindowCache.java | 4 +- .../java/JavaAdvancementsTabTranslator.java | 45 +++ .../java/JavaAdvancementsTranslator.java | 103 ++++++ .../connector/utils/GeyserAdvancement.java | 89 +++++ connector/src/main/resources/languages | 2 +- 10 files changed, 654 insertions(+), 16 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/GeyserAdvancement.java 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 469d613c717..d31983eb45a 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 CommandManager(GeyserConnector connector) { registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version")); registerCommand(new SettingsCommand(connector, "settings", "geyser.commands.settings.desc", "geyser.command.settings")); registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); + registerCommand(new AdvancementsCommand(connector, "advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); } public void registerCommand(GeyserCommand command) { diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java new file mode 100644 index 00000000000..3067f3d5357 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019-2021 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 org.geysermc.common.window.SimpleFormWindow; +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.network.session.cache.AdvancementsCache; + +public class AdvancementsCommand extends GeyserCommand { + + private final GeyserConnector connector; + + public AdvancementsCommand(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; + + SimpleFormWindow window = session.getAdvancementsCache().buildMenuForm(); + session.sendForm(window, AdvancementsCache.ADVANCEMENTS_MENU_FORM_ID); + } + + @Override + public boolean isExecutableOnConsole() { + return false; + } +} 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 3922a95ffc6..7ebfaeda559 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -33,14 +33,9 @@ import org.geysermc.connector.common.AuthType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.cache.AdvancementsCache; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; -import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.utils.LoginEncryptionUtils; -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 org.geysermc.connector.utils.*; import java.io.FileInputStream; import java.io.InputStream; @@ -144,12 +139,19 @@ public boolean handle(ResourcePackClientResponsePacket packet) { @Override 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()); + switch (packet.getFormId()) { + case AdvancementsCache.ADVANCEMENT_INFO_FORM_ID: + return session.getAdvancementsCache().handleInfoForm(packet.getFormData()); + case AdvancementsCache.ADVANCEMENTS_LIST_FORM_ID: + return session.getAdvancementsCache().handleListForm(packet.getFormData()); + case AdvancementsCache.ADVANCEMENTS_MENU_FORM_ID: + return session.getAdvancementsCache().handleMenuForm(packet.getFormData()); + case SettingsUtils.SETTINGS_FORM_ID: + return SettingsUtils.handleSettingsForm(session, packet.getFormData()); + case StatisticsUtils.STATISTICS_LIST_FORM_ID: + return StatisticsUtils.handleListForm(session, packet.getFormData()); + case StatisticsUtils.STATISTICS_MENU_FORM_ID: + return StatisticsUtils.handleMenuForm(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 a82ea061e5a..5b43fec0446 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 @@ -121,6 +121,7 @@ public class GeyserSession implements CommandSender { private final SessionPlayerEntity playerEntity; private PlayerInventory inventory; + private AdvancementsCache advancementsCache; private BookEditCache bookEditCache; private ChunkCache chunkCache; private EntityCache entityCache; @@ -350,6 +351,7 @@ public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServ this.connector = connector; this.upstream = new UpstreamSession(bedrockServerSession); + this.advancementsCache = new AdvancementsCache(this); this.bookEditCache = new BookEditCache(this); this.chunkCache = new ChunkCache(this); this.entityCache = new EntityCache(this); @@ -684,6 +686,7 @@ public void disconnect(String reason) { tickThread.cancel(true); } + this.advancementsCache = null; this.bookEditCache = null; this.chunkCache = null; this.entityCache = null; diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java new file mode 100644 index 00000000000..369967accdb --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2019-2021 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.session.cache; + +import com.github.steveice10.mc.protocol.data.game.advancement.Advancement; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientAdvancementTabPacket; +import lombok.Getter; +import lombok.Setter; +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.chat.MessageTranslator; +import org.geysermc.connector.utils.GeyserAdvancement; +import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.connector.utils.LocaleUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AdvancementsCache { + + // Different form IDs + public static final int ADVANCEMENTS_MENU_FORM_ID = 1341; + public static final int ADVANCEMENTS_LIST_FORM_ID = 1342; + public static final int ADVANCEMENT_INFO_FORM_ID = 1343; + + /** + * Stores the player's advancement progress + */ + @Getter + private final Map> storedAdvancementProgress = new HashMap<>(); + + /** + * Stores advancements for the player. + */ + @Getter + private final Map storedAdvancements = new HashMap<>(); + + /** + * Stores player's chosen advancement's ID and title for use in form creators. + */ + @Setter + private String currentAdvancementCategoryId = null; + + private final GeyserSession session; + + public AdvancementsCache(GeyserSession session) { + this.session = session; + } + + /** + * Build a form with all advancement categories + * + * @return The built advancement category menu + */ + public SimpleFormWindow buildMenuForm() { + // Cache the language for cleaner access + String language = session.getClientData().getLanguageCode(); + + // Created menu window for advancement categories + SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.advancements", language), ""); + for (Map.Entry advancement : storedAdvancements.entrySet()) { + if (advancement.getValue().getParentId() == null) { // No parent means this is a root advancement + window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), language))); + } + } + + if (window.getButtons().isEmpty()) { + window.setContent(LocaleUtils.getLocaleString("advancements.empty", language)); + } + + return window; + } + + /** + * Builds the list of advancements + * + * @return The built list form + */ + public SimpleFormWindow buildListForm() { + // Cache the language for easier access + String language = session.getLocale(); + String id = currentAdvancementCategoryId; + GeyserAdvancement categoryAdvancement = storedAdvancements.get(currentAdvancementCategoryId); + + // Create the window + SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language), + MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language)); + + if (id != null) { + for (Map.Entry advancementEntry : storedAdvancements.entrySet()) { + GeyserAdvancement advancement = advancementEntry.getValue(); + if (advancement != null) { + if (advancement.getParentId() != null && currentAdvancementCategoryId.equals(advancement.getRootId(this))) { + boolean earned = isEarned(advancement); + + if (earned || !advancement.getDisplayData().isShowToast()) { + window.getButtons().add(new FormButton("§6" + MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n")); + } else { + window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n")); + } + } + } + } + } + + window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language))); + + return window; + } + + /** + * Builds the advancement display info based on the chosen category + * + * @param advancement The advancement used to create the info display + * @return The information for the chosen advancement + */ + public SimpleFormWindow buildInfoForm(GeyserAdvancement advancement) { + // Cache language for easier access + String language = session.getLocale(); + + String earned = isEarned(advancement) ? "yes" : "no"; + + String description = getColorFromAdvancementFrameType(advancement) + MessageTranslator.convertMessage(advancement.getDisplayData().getDescription(), language); + String earnedString = LanguageUtils.getPlayerLocaleString("geyser.advancements.earned", language, LocaleUtils.getLocaleString("gui." + earned, language)); + + /* + Layout will look like: + + (Form title) Stone Age + + (Description) Mine stone with your new pickaxe + + Earned: Yes + Parent Advancement: Minecraft // If relevant + */ + + String content = description + "\n\n§f" + + earnedString + "\n"; + if (!currentAdvancementCategoryId.equals(advancement.getParentId())) { + // Only display the parent if it is not the category + content += LanguageUtils.getPlayerLocaleString("geyser.advancements.parentid", language, MessageTranslator.convertMessage(storedAdvancements.get(advancement.getParentId()).getDisplayData().getTitle(), language)); + } + SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()), content); + window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language))); + + return window; + } + + /** + * Determine if this advancement has been earned. + * + * @param advancement the advancement to determine + * @return true if the advancement has been earned. + */ + public boolean isEarned(GeyserAdvancement advancement) { + boolean earned = false; + if (advancement.getRequirements().size() == 0) { + // Minecraft handles this case, so we better as well + return false; + } + Map progress = storedAdvancementProgress.get(advancement.getId()); + if (progress != null) { + // Each advancement's requirement must be fulfilled + // For example, [[zombie, blaze, skeleton]] means that one of those three categories must be achieved + // But [[zombie], [blaze], [skeleton]] means that all three requirements must be completed + for (List requirements : advancement.getRequirements()) { + boolean requirementsDone = false; + for (String requirement : requirements) { + Long obtained = progress.get(requirement); + // -1 means that this particular component required for completing the advancement + // has yet to be fulfilled + if (obtained != null && !obtained.equals(-1L)) { + requirementsDone = true; + break; + } + } + if (!requirementsDone) { + return false; + } + } + earned = true; + } + return earned; + } + + /** + * Handle the menu form response + * + * @param response The response string to parse + * @return True if the form was parsed correctly, false if not + */ + public boolean handleMenuForm(String response) { + SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_MENU_FORM_ID); + menuForm.setResponse(response); + + SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse(); + + String id = ""; + if (formResponse != null && formResponse.getClickedButton() != null) { + int advancementIndex = 0; + for (Map.Entry advancement : storedAdvancements.entrySet()) { + if (advancement.getValue().getParentId() == null) { // Root advancement + if (advancementIndex == formResponse.getClickedButtonId()) { + id = advancement.getKey(); + break; + } else { + advancementIndex++; + } + } + } + } + if (!id.equals("")) { + if (id.equals(currentAdvancementCategoryId)) { + // The server thinks we are already on this tab + session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID); + } else { + // Send a packet indicating that we intend to open this particular advancement window + ClientAdvancementTabPacket packet = new ClientAdvancementTabPacket(id); + session.sendDownstreamPacket(packet); + // Wait for a response there + } + } + + return true; + } + + /** + * Handle the list form response (Advancement category choice) + * + * @param response The response string to parse + * @return True if the form was parsed correctly, false if not + */ + public boolean handleListForm(String response) { + SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_LIST_FORM_ID); + listForm.setResponse(response); + + SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse(); + + if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) { + GeyserAdvancement advancement = null; + int advancementIndex = 0; + // Loop around to find the advancement that the client pressed + for (GeyserAdvancement advancementEntry : storedAdvancements.values()) { + if (advancementEntry.getParentId() != null && + currentAdvancementCategoryId.equals(advancementEntry.getRootId(this))) { + if (advancementIndex == formResponse.getClickedButtonId()) { + advancement = advancementEntry; + break; + } else { + advancementIndex++; + } + } + } + if (advancement != null) { + session.sendForm(buildInfoForm(advancement), ADVANCEMENT_INFO_FORM_ID); + } else { + session.sendForm(buildMenuForm(), ADVANCEMENTS_MENU_FORM_ID); + // Indicate that we have closed the current advancement tab + session.sendDownstreamPacket(new ClientAdvancementTabPacket()); + } + } else { + // Indicate that we have closed the current advancement tab + session.sendDownstreamPacket(new ClientAdvancementTabPacket()); + } + + return true; + } + + /** + * Handle the info form response + * + * @param response The response string to parse + * @return True if the form was parsed correctly, false if not + */ + public boolean handleInfoForm(String response) { + SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENT_INFO_FORM_ID); + listForm.setResponse(response); + + SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse(); + + if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) { + session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID); + } + + return true; + } + + public String getColorFromAdvancementFrameType(GeyserAdvancement advancement) { + String base = "\u00a7"; + if (advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE) { + return base + "5"; + } + return base + "a"; // Used for types TASK and GOAL + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java index d9625ff6729..a114b8bbc6e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java @@ -37,10 +37,10 @@ public class WindowCache { - private GeyserSession session; + private final GeyserSession session; @Getter - private Int2ObjectMap windows = new Int2ObjectOpenHashMap<>(); + private final Int2ObjectMap windows = new Int2ObjectOpenHashMap<>(); public WindowCache(GeyserSession session) { this.session = session; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java new file mode 100644 index 00000000000..17a3b3792e9 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019-2021 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.ServerAdvancementTabPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.cache.AdvancementsCache; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +/** + * Indicates that the client should open a particular advancement tab + */ +@Translator(packet = ServerAdvancementTabPacket.class) +public class JavaAdvancementsTabTranslator extends PacketTranslator { + + @Override + public void translate(ServerAdvancementTabPacket packet, GeyserSession session) { + session.getAdvancementsCache().setCurrentAdvancementCategoryId(packet.getTabId()); + session.sendForm(session.getAdvancementsCache().buildListForm(), AdvancementsCache.ADVANCEMENTS_LIST_FORM_ID); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTranslator.java new file mode 100644 index 00000000000..714578e9a46 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTranslator.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java; + +import com.github.steveice10.mc.protocol.data.game.advancement.Advancement; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerAdvancementsPacket; +import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; +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.chat.MessageTranslator; +import org.geysermc.connector.network.session.cache.AdvancementsCache; +import org.geysermc.connector.utils.GeyserAdvancement; +import org.geysermc.connector.utils.LocaleUtils; + +import java.util.Map; + +@Translator(packet = ServerAdvancementsPacket.class) +public class JavaAdvancementsTranslator extends PacketTranslator { + + @Override + public void translate(ServerAdvancementsPacket packet, GeyserSession session) { + AdvancementsCache advancementsCache = session.getAdvancementsCache(); + if (packet.isReset()) { + advancementsCache.getStoredAdvancements().clear(); + advancementsCache.getStoredAdvancementProgress().clear(); + } + + // Removes removed advancements from player's stored advancements + for (String removedAdvancement : packet.getRemovedAdvancements()) { + advancementsCache.getStoredAdvancements().remove(removedAdvancement); + } + + advancementsCache.getStoredAdvancementProgress().putAll(packet.getProgress()); + + sendToolbarAdvancementUpdates(session, packet); + + // Adds advancements to the player's stored advancements when advancements are sent + for (Advancement advancement : packet.getAdvancements()) { + if (advancement.getDisplayData() != null && !advancement.getDisplayData().isHidden()) { + GeyserAdvancement geyserAdvancement = GeyserAdvancement.from(advancement); + advancementsCache.getStoredAdvancements().put(advancement.getId(), geyserAdvancement); + } else { + advancementsCache.getStoredAdvancements().remove(advancement.getId()); + } + } + } + + /** + * Handle all advancements progress updates + */ + public void sendToolbarAdvancementUpdates(GeyserSession session, ServerAdvancementsPacket packet) { + if (packet.isReset()) { + // Advancements are being cleared, so they can't be granted + return; + } + for (Map.Entry> progress : packet.getProgress().entrySet()) { + GeyserAdvancement advancement = session.getAdvancementsCache().getStoredAdvancements().get(progress.getKey()); + if (advancement != null && advancement.getDisplayData() != null) { + if (session.getAdvancementsCache().isEarned(advancement)) { + // Java uses some pink color for toast challenge completes + String color = advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE ? + "§d" : "§a"; + String advancementName = MessageTranslator.convertMessage(advancement.getDisplayData().getTitle(), session.getLocale()); + + // Send an action bar message stating they earned an achievement + // Sent for instances where broadcasting advancements through chat are disabled + SetTitlePacket titlePacket = new SetTitlePacket(); + titlePacket.setText(color + "[" + LocaleUtils.getLocaleString("advancements.toast." + + advancement.getDisplayData().getFrameType().toString().toLowerCase(), session.getLocale()) + "]§f " + advancementName); + titlePacket.setType(SetTitlePacket.Type.ACTIONBAR); + titlePacket.setFadeOutTime(3); + titlePacket.setFadeInTime(3); + titlePacket.setStayTime(3); + session.sendUpstreamPacket(titlePacket); + } + } + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/GeyserAdvancement.java b/connector/src/main/java/org/geysermc/connector/utils/GeyserAdvancement.java new file mode 100644 index 00000000000..31560498a30 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/GeyserAdvancement.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +import com.github.steveice10.mc.protocol.data.game.advancement.Advancement; +import lombok.NonNull; +import org.geysermc.connector.network.session.cache.AdvancementsCache; + +import java.util.List; + +/** + * A wrapper around MCProtocolLib's {@link Advancement} class so we can control the parent of an advancement + */ +public class GeyserAdvancement { + private final Advancement advancement; + private String rootId = null; + + public static GeyserAdvancement from(Advancement advancement) { + return new GeyserAdvancement(advancement); + } + + private GeyserAdvancement(Advancement advancement) { + this.advancement = advancement; + } + + @NonNull + public String getId() { + return this.advancement.getId(); + } + + @NonNull + public List getCriteria() { + return this.advancement.getCriteria(); + } + + @NonNull + public List> getRequirements() { + return this.advancement.getRequirements(); + } + + public String getParentId() { + return this.advancement.getParentId(); + } + + public Advancement.DisplayData getDisplayData() { + return this.advancement.getDisplayData(); + } + + public String getRootId(AdvancementsCache advancementsCache) { + if (rootId == null) { + if (this.advancement.getParentId() == null) { + // We are the root ID + this.rootId = this.advancement.getId(); + } else { + // Go through our cache, and descend until we find the root ID + GeyserAdvancement advancement = advancementsCache.getStoredAdvancements().get(this.advancement.getParentId()); + if (advancement.getParentId() == null) { + this.rootId = advancement.getId(); + } else { + this.rootId = advancement.getRootId(advancementsCache); + } + } + } + return rootId; + } +} diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index 6f246c24ddb..8141bc6aed8 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 6f246c24ddbd543a359d651e706da470fe53ceeb +Subproject commit 8141bc6aed878a95ed9ee3ca83a2381f9906c4b4 From af405f320a2669fcf4ddb3bc15eac956878d9e4a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 12 Jan 2021 14:42:53 -0500 Subject: [PATCH 19/37] Prevent CME when adding players' emotes (#1831) --- .../org/geysermc/connector/network/session/GeyserSession.java | 4 +++- 1 file changed, 3 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 5b43fec0446..104e72cd365 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 @@ -370,7 +370,9 @@ public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServ this.inventoryCache.getInventories().put(0, inventory); - connector.getPlayers().forEach(player -> this.emotes.addAll(player.getEmotes())); + // Make a copy to prevent ConcurrentModificationException + final List tmpPlayers = new ArrayList<>(connector.getPlayers()); + tmpPlayers.forEach(player -> this.emotes.addAll(player.getEmotes())); bedrockServerSession.addDisconnectHandler(disconnectReason -> { connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", bedrockServerSession.getAddress().getAddress(), disconnectReason)); From dd0b4bafe8f76dfdc7fd8a3dd6d593db922b9994 Mon Sep 17 00:00:00 2001 From: Extollite <42713788+Extollite@users.noreply.github.com> Date: Tue, 12 Jan 2021 21:06:48 +0100 Subject: [PATCH 20/37] Close locale streams (#1832) * Close locale streams * Fix formatting --- .../main/java/org/geysermc/connector/utils/FileUtils.java | 4 ++-- .../main/java/org/geysermc/connector/utils/LocaleUtils.java | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java index 862af548df6..d1dd6fd7845 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -217,8 +217,8 @@ public static Reflections getReflections(String path) { * @return The byte array of the file */ public static byte[] readAllBytes(File file) { - try { - return readAllBytes(new FileInputStream(file)); + try (InputStream inputStream = new FileInputStream(file)) { + return readAllBytes(inputStream); } catch (IOException e) { throw new RuntimeException("Cannot read " + file); } 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 e180682d6b1..8619eaf0d12 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -208,6 +208,12 @@ private static void loadLocale(String locale) { // Insert the locale into the mappings LOCALE_MAPPINGS.put(locale.toLowerCase(), langMap); + + try { + localeStream.close(); + } catch (IOException e) { + throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.locale.fail.file", locale, e.getMessage())); + } } else { GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.locale.fail.missing", locale)); } From c0a64659c24a137c2d606ce2369de91bb446fc7b Mon Sep 17 00:00:00 2001 From: Extollite <42713788+Extollite@users.noreply.github.com> Date: Tue, 12 Jan 2021 21:41:15 +0100 Subject: [PATCH 21/37] Close en_us.hash stream (#1833) --- .../main/java/org/geysermc/connector/utils/LocaleUtils.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 8619eaf0d12..f2ec43bf63b 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -142,8 +142,9 @@ private static void downloadLocale(String locale) { try { File hashFile = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales/en_us.hash").toFile(); if (hashFile.exists()) { - BufferedReader br = new BufferedReader(new FileReader(hashFile)); - curHash = br.readLine().trim(); + try (BufferedReader br = new BufferedReader(new FileReader(hashFile))) { + curHash = br.readLine().trim(); + } } } catch (IOException ignored) { } targetHash = clientJarInfo.getSha1(); From 6fdfcfb35c46c7dd5a707c8413e98ce3058fe635 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 13 Jan 2021 14:28:39 -0500 Subject: [PATCH 22/37] Clarify that we do support 1.16.201 (#1836) --- .../org/geysermc/connector/network/BedrockProtocol.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java index fbc7849fbaf..d24cea3285d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java +++ b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java @@ -40,7 +40,9 @@ public class BedrockProtocol { * Default Bedrock codec that should act as a fallback. Should represent the latest available * release of the game that Geyser supports. */ - public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v422.V422_CODEC; + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v422.V422_CODEC.toBuilder() + .minecraftVersion("1.16.201") + .build(); /** * A list of all supported Bedrock versions that can join Geyser */ @@ -50,7 +52,9 @@ public class BedrockProtocol { SUPPORTED_BEDROCK_CODECS.add(Bedrock_v419.V419_CODEC.toBuilder() .minecraftVersion("1.16.100/1.16.101") // We change this as 1.16.100.60 is a beta .build()); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + .minecraftVersion("1.16.200/1.16.201") + .build()); } /** From 8e5a6efa042b52c08748fdf6a1f65fd55f6ac140 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 13 Jan 2021 12:29:34 -0700 Subject: [PATCH 23/37] Update TODO inventory-blocks: grindstone (#1827) Re: https://github.com/GeyserMC/Geyser/issues/1087 https://github.com/GeyserMC/Geyser/issues/1670 This is seemingly not yet fixed on 1.2.0-SNAPSHOT-561 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1d1657edce8..ad001d63a5c 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set - Horse Inventory - Loom - Smithing Table + - Grindstone ## What can't be fixed The following things can't be fixed because of Bedrock limitations. They might be fixable in the future, but not as of now. From 16fdf51d760cd05382dcf8c9ca5c3c3282443d9b Mon Sep 17 00:00:00 2001 From: rtm516 Date: Thu, 14 Jan 2021 00:15:00 +0000 Subject: [PATCH 24/37] Disable build notifications for sub-builds and build GeyserConnect (#1838) Co-authored-by: EasyClifton <63668444+EasyClifton@users.noreply.github.com> --- Jenkinsfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 50149136155..6564bd1f937 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -92,8 +92,9 @@ pipeline { success { script { if (env.BRANCH_NAME == 'master') { - build propagate: false, wait: false, job: 'GeyserMC/Geyser-Fabric/java-1.16' - build propagate: false, wait: false, job: 'GeyserMC/GeyserAndroid/master' + build propagate: false, wait: false, job: 'GeyserMC/Geyser-Fabric/java-1.16', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)] + build propagate: false, wait: false, job: 'GeyserMC/GeyserAndroid/master', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)] + build propagate: false, wait: false, job: 'GeyserMC/GeyserConnect/master', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)] } } } From 12fb0b6d6d67f93d8ba1c0ca7f920778e495e627 Mon Sep 17 00:00:00 2001 From: Redned Date: Fri, 15 Jan 2021 21:50:35 -0600 Subject: [PATCH 25/37] Include 1.16.5 in README as a supported Java version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ad001d63a5c..68f98c01872 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have now joined us here! -### Currently supporting Minecraft Bedrock v1.16.100 - v1.16.201 and Minecraft Java v1.16.4. +### Currently supporting Minecraft Bedrock v1.16.100 - v1.16.201 and Minecraft Java v1.16.4 - v1.16.5. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. From da512da511efac6cebb6bb29d5a0e8e2bc514a43 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Fri, 15 Jan 2021 21:59:41 -0600 Subject: [PATCH 26/37] Include 1.16.5 in version command too --- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 1 + .../org/geysermc/connector/command/defaults/VersionCommand.java | 2 +- 2 files changed, 2 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 d61500e0455..d1059627a8d 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -85,6 +85,7 @@ public class GeyserConnector { public static final String NAME = "Geyser"; public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs public static final String VERSION = "DEV"; // A fallback for running in IDEs + public static final String MINECRAFT_VERSION = "1.16.4 - 1.16.5"; /** * Oauth client ID for Microsoft authentication 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 1f807cf632d..e0c445b3f2f 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,7 +61,7 @@ public void execute(CommandSender sender, String[] args) { bedrockVersions = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion(); } - sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.version", sender.getLocale(), GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, bedrockVersions)); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.version", sender.getLocale(), GeyserConnector.NAME, GeyserConnector.VERSION, GeyserConnector.MINECRAFT_VERSION, bedrockVersions)); // Disable update checking in dev mode //noinspection ConstantConditions - changes in production From 2d9baf1bfc46fd5d3da89bdbf26620423f74aafb Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 16 Jan 2021 22:18:13 -0500 Subject: [PATCH 27/37] Send message to Java if command is Bedrock-only (#1834) * Send message to Java if command is Bedrock-only If a Java player attempts to use a Bedrock-only command, such as `geyser statistics`, they will get an error message stating that this command is only for Bedrock players. This commit also cleans up Velocity Adventure dependency usage. Issues were caused because of the way relocation works and because Velocity also uses Adventure. * Only look for a session if we have to * Update languages submodule --- .../command/GeyserBungeeCommandExecutor.java | 29 +++++++--- .../command/GeyserSpigotCommandExecutor.java | 35 +++++++----- .../command/GeyserSpongeCommandExecutor.java | 37 +++++++----- .../standalone/gui/GeyserStandaloneGUI.java | 6 +- bootstrap/velocity/pom.xml | 11 +++- .../GeyserVelocityPingPassthrough.java | 7 +-- .../GeyserVelocityCommandExecutor.java | 32 ++++++----- .../connector/command/CommandExecutor.java | 56 +++++++++++++++++++ .../connector/command/CommandManager.java | 11 +++- .../connector/command/GeyserCommand.java | 13 ++++- .../command/defaults/AdvancementsCommand.java | 28 ++-------- .../command/defaults/DumpCommand.java | 3 +- .../command/defaults/HelpCommand.java | 3 +- .../command/defaults/ListCommand.java | 2 +- .../command/defaults/OffhandCommand.java | 29 ++++------ .../command/defaults/ReloadCommand.java | 8 +-- .../command/defaults/SettingsCommand.java | 25 +++------ .../command/defaults/StatisticsCommand.java | 29 +++------- .../command/defaults/StopCommand.java | 5 +- .../command/defaults/VersionCommand.java | 6 +- .../BedrockCommandRequestTranslator.java | 2 +- connector/src/main/resources/languages | 2 +- 22 files changed, 222 insertions(+), 157 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/command/CommandExecutor.java 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 2431f0a4e37..b391d7b1c58 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 @@ -30,7 +30,9 @@ 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.CommandExecutor; import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import java.util.ArrayList; @@ -38,29 +40,42 @@ public class GeyserBungeeCommandExecutor extends Command implements TabExecutor { + private final CommandExecutor commandExecutor; private final GeyserConnector connector; public GeyserBungeeCommandExecutor(GeyserConnector connector) { super("geyser"); + this.commandExecutor = new CommandExecutor(connector); this.connector = connector; } @Override public void execute(CommandSender sender, String[] args) { if (args.length > 0) { - if (getCommand(args[0]) != null) { - if (!sender.hasPermission(getCommand(args[0]).getPermission())) { - BungeeCommandSender commandSender = new BungeeCommandSender(sender); + GeyserCommand command = this.commandExecutor.getCommand(args[0]); + if (command != null) { + BungeeCommandSender commandSender = new BungeeCommandSender(sender); + if (!sender.hasPermission(command.getPermission())) { String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale()); 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]); + GeyserSession session = null; + if (command.isBedrockOnly()) { + session = this.commandExecutor.getGeyserSession(commandSender); + if (session == null) { + String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.getLocale()); + + commandSender.sendMessage(ChatColor.RED + message); + return; + } + } + command.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); } } else { - getCommand("help").execute(new BungeeCommandSender(sender), new String[0]); + this.commandExecutor.getCommand("help").execute(null, new BungeeCommandSender(sender), new String[0]); } } @@ -71,8 +86,4 @@ public Iterable onTabComplete(CommandSender sender, String[] args) { } return new ArrayList<>(); } - - private GeyserCommand getCommand(String label) { - return connector.getCommandManager().getCommands().get(label); - } } 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 1db86856fbe..6cdcdae67d3 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 @@ -25,40 +25,51 @@ package org.geysermc.platform.spigot.command; -import lombok.AllArgsConstructor; import org.bukkit.ChatColor; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandExecutor; import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -@AllArgsConstructor -public class GeyserSpigotCommandExecutor implements TabExecutor { +public class GeyserSpigotCommandExecutor extends CommandExecutor implements TabExecutor { - private final GeyserConnector connector; + public GeyserSpigotCommandExecutor(GeyserConnector connector) { + super(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())) { - SpigotCommandSender commandSender = new SpigotCommandSender(sender); - String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale());; + GeyserCommand geyserCommand = getCommand(args[0]); + if (geyserCommand != null) { + SpigotCommandSender commandSender = new SpigotCommandSender(sender); + if (!sender.hasPermission(geyserCommand.getPermission())) { + String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale()); 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]); + GeyserSession session = null; + if (geyserCommand.isBedrockOnly()) { + session = getGeyserSession(commandSender); + if (session == null) { + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.getLocale())); + return true; + } + } + geyserCommand.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); return true; } } else { - getCommand("help").execute(new SpigotCommandSender(sender), new String[0]); + getCommand("help").execute(null, new SpigotCommandSender(sender), new String[0]); return true; } return true; @@ -71,8 +82,4 @@ public List onTabComplete(CommandSender sender, Command command, String } return new ArrayList<>(); } - - private GeyserCommand getCommand(String label) { - return connector.getCommandManager().getCommands().get(label); - } } 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 938d1992842..8ef23b19e4a 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 @@ -25,10 +25,12 @@ package org.geysermc.platform.sponge.command; -import lombok.AllArgsConstructor; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.common.ChatColor; +import org.geysermc.connector.command.CommandExecutor; +import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.common.ChatColor; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import org.spongepowered.api.command.CommandCallable; import org.spongepowered.api.command.CommandException; @@ -44,25 +46,36 @@ import java.util.List; import java.util.Optional; -@AllArgsConstructor -public class GeyserSpongeCommandExecutor implements CommandCallable { +public class GeyserSpongeCommandExecutor extends CommandExecutor implements CommandCallable { - private GeyserConnector connector; + public GeyserSpongeCommandExecutor(GeyserConnector connector) { + super(connector); + } @Override - public CommandResult process(CommandSource source, String arguments) throws CommandException { + public CommandResult process(CommandSource source, String arguments) { String[] args = arguments.split(" "); if (args.length > 0) { - if (getCommand(args[0]) != null) { - if (!source.hasPermission(getCommand(args[0]).getPermission())) { + GeyserCommand command = getCommand(args[0]); + if (command != null) { + CommandSender commandSender = new SpongeCommandSender(source); + if (!source.hasPermission(command.getPermission())) { // Not ideal to use log here but we dont get a session source.sendMessage(Text.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"))); return CommandResult.success(); } - getCommand(args[0]).execute(new SpongeCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); + GeyserSession session = null; + if (command.isBedrockOnly()) { + session = getGeyserSession(commandSender); + if (session == null) { + source.sendMessage(Text.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.bedrock_only"))); + return CommandResult.success(); + } + } + getCommand(args[0]).execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); } } else { - getCommand("help").execute(new SpongeCommandSender(source), new String[0]); + getCommand("help").execute(null, new SpongeCommandSender(source), new String[0]); } return CommandResult.success(); } @@ -94,8 +107,4 @@ public Optional getHelp(CommandSource source) { public Text getUsage(CommandSource source) { return Text.of("/geyser help"); } - - private GeyserCommand getCommand(String label) { - return connector.getCommandManager().getCommands().get(label); - } } 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 fb6a46f9f8d..3636dded873 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 @@ -271,17 +271,17 @@ public void setupInterface(GeyserStandaloneLogger geyserStandaloneLogger, Geyser JMenuItem commandButton = hasSubCommands ? new JMenu(command.getValue().getName()) : new JMenuItem(command.getValue().getName()); commandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription()); if (!hasSubCommands) { - commandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ })); + commandButton.addActionListener(e -> command.getValue().execute(null, 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[]{ })); + otherCommandButton.addActionListener(e -> command.getValue().execute(null, 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})); + item.addActionListener(e -> command.getValue().execute(null, geyserStandaloneLogger, new String[]{subCommandName})); commandButton.add(item); } } diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 5c0824def5e..58eee1f77ea 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -82,8 +82,8 @@ org.geysermc.platform.velocity.shaded.dom4j - net.kyori - org.geysermc.platform.velocity.shaded.kyori + net.kyori.adventure.text.serializer.gson.legacyimpl + org.geysermc.platform.velocity.shaded.kyori.legacyimpl @@ -105,6 +105,13 @@ io.netty:netty-codec:* org.slf4j:* org.ow2.asm:* + + net.kyori:adventure-api:* + net.kyori:examination-api:* + net.kyori:examination-string:* + net.kyori:adventure-text-serializer-gson:* + net.kyori:adventure-text-serializer-legacy:* + net.kyori:adventure-nbt:* diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPingPassthrough.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPingPassthrough.java index bab0e350518..bc10bc723c0 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPingPassthrough.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPingPassthrough.java @@ -31,11 +31,10 @@ import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.server.ServerPing; import lombok.AllArgsConstructor; -import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.geysermc.connector.common.ping.GeyserPingInfo; import org.geysermc.connector.ping.IGeyserPingPassthrough; -import java.net.Inet4Address; import java.net.InetSocketAddress; import java.util.Optional; import java.util.concurrent.ExecutionException; @@ -50,13 +49,13 @@ public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) { ProxyPingEvent event; try { event = server.getEventManager().fire(new ProxyPingEvent(new GeyserInboundConnection(inetSocketAddress), ServerPing.builder() - .description(server.getConfiguration().getMotdComponent()).onlinePlayers(server.getPlayerCount()) + .description(server.getConfiguration().getMotd()).onlinePlayers(server.getPlayerCount()) .maximumPlayers(server.getConfiguration().getShowMaxPlayers()).build())).get(); } catch (ExecutionException | InterruptedException e) { throw new RuntimeException(e); } GeyserPingInfo geyserPingInfo = new GeyserPingInfo( - LegacyComponentSerializer.legacy().serialize(event.getPing().getDescription(), '§'), + LegacyComponentSerializer.legacy('§').serialize(event.getPing().getDescriptionComponent()), new GeyserPingInfo.Players( event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getMax(), event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getOnline() 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 4aab73e5994..c8998d8feda 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java @@ -25,37 +25,47 @@ package org.geysermc.platform.velocity.command; -import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; -import lombok.AllArgsConstructor; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandExecutor; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.common.ChatColor; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -@AllArgsConstructor -public class GeyserVelocityCommandExecutor implements SimpleCommand { +public class GeyserVelocityCommandExecutor extends CommandExecutor implements SimpleCommand { - private final GeyserConnector connector; + public GeyserVelocityCommandExecutor(GeyserConnector connector) { + super(connector); + } @Override public void execute(Invocation invocation) { if (invocation.arguments().length > 0) { - if (getCommand(invocation.arguments()[0]) != null) { + GeyserCommand command = getCommand(invocation.arguments()[0]); + if (command != null) { + CommandSender sender = new VelocityCommandSender(invocation.source()); if (!invocation.source().hasPermission(getCommand(invocation.arguments()[0]).getPermission())) { - CommandSender sender = new VelocityCommandSender(invocation.source()); sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale())); return; } - getCommand(invocation.arguments()[0]).execute(new VelocityCommandSender(invocation.source()), invocation.arguments().length > 1 ? Arrays.copyOfRange(invocation.arguments(), 1, invocation.arguments().length) : new String[0]); + GeyserSession session = null; + if (command.isBedrockOnly()) { + session = getGeyserSession(sender); + if (session == null) { + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.getLocale())); + return; + } + } + command.execute(session, sender, invocation.arguments().length > 1 ? Arrays.copyOfRange(invocation.arguments(), 1, invocation.arguments().length) : new String[0]); } } else { - getCommand("help").execute(new VelocityCommandSender(invocation.source()), new String[0]); + getCommand("help").execute(null, new VelocityCommandSender(invocation.source()), new String[0]); } } @@ -66,8 +76,4 @@ public List suggest(Invocation invocation) { } return new ArrayList<>(); } - - private GeyserCommand getCommand(String label) { - return connector.getCommandManager().getCommands().get(label); - } } diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandExecutor.java b/connector/src/main/java/org/geysermc/connector/command/CommandExecutor.java new file mode 100644 index 00000000000..751f5126069 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/command/CommandExecutor.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2021 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; + +import lombok.AllArgsConstructor; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; + +/** + * Represents helper functions for listening to {@code /geyser} commands. + */ +@AllArgsConstructor +public class CommandExecutor { + + protected final GeyserConnector connector; + + public GeyserCommand getCommand(String label) { + return connector.getCommandManager().getCommands().get(label); + } + + public GeyserSession getGeyserSession(CommandSender sender) { + if (sender.isConsole()) { + return null; + } + + for (GeyserSession session : connector.getPlayers()) { + if (sender.getName().equals(session.getPlayerEntity().getUsername())) { + return session; + } + } + return null; + } +} 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 d31983eb45a..9f675ae81ed 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -29,6 +29,7 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.defaults.*; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import java.util.*; @@ -89,7 +90,15 @@ public void runCommand(CommandSender sender, String command) { return; } - cmd.execute(sender, args); + if (sender instanceof GeyserSession) { + cmd.execute((GeyserSession) sender, sender, args); + } else { + if (!cmd.isBedrockOnly()) { + cmd.execute(null, sender, args); + } else { + connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.bedrock_only")); + } + } } /** 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 c606e2e7b5d..48fe2eb9a86 100644 --- a/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java @@ -28,7 +28,9 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; +import org.geysermc.connector.network.session.GeyserSession; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -47,7 +49,7 @@ public abstract class GeyserCommand { @Setter private List aliases = new ArrayList<>(); - public abstract void execute(CommandSender sender, String[] args); + public abstract void execute(@Nullable GeyserSession session, CommandSender sender, String[] args); /** * If false, hides the command from being shown on the Geyser Standalone GUI. @@ -75,4 +77,13 @@ public List getSubCommands() { public boolean hasSubCommands() { return !getSubCommands().isEmpty(); } + + /** + * Used to send a deny message to Java players if this command can only be used by Bedrock players. + * + * @return true if this command can only be used by Bedrock players. + */ + public boolean isBedrockOnly() { + return false; + } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java index 3067f3d5357..2ef23381b99 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java @@ -34,33 +34,12 @@ public class AdvancementsCommand extends GeyserCommand { - private final GeyserConnector connector; - public AdvancementsCommand(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; - } - } - } + public void execute(GeyserSession session, CommandSender sender, String[] args) { if (session == null) return; SimpleFormWindow window = session.getAdvancementsCache().buildMenuForm(); @@ -71,4 +50,9 @@ public void execute(CommandSender sender, String[] args) { public boolean isExecutableOnConsole() { return false; } + + @Override + public boolean isBedrockOnly() { + return true; + } } 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 5bc3efea7e6..97d09f7e05c 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 @@ -33,6 +33,7 @@ import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.common.serializer.AsteriskSerializer; import org.geysermc.connector.dump.DumpInfo; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.WebUtils; @@ -54,7 +55,7 @@ public DumpCommand(GeyserConnector connector, String name, String description, S } @Override - public void execute(CommandSender sender, String[] args) { + public void execute(GeyserSession session, CommandSender sender, String[] args) { boolean showSensitive = false; boolean offlineDump = false; if (args.length >= 1) { 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 7ab3aec3c64..c2716f20695 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 @@ -29,6 +29,7 @@ import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.common.ChatColor; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import java.util.Collections; @@ -48,7 +49,7 @@ public HelpCommand(GeyserConnector connector, String name, String description, S } @Override - public void execute(CommandSender sender, String[] args) { + public void execute(GeyserSession session, CommandSender sender, String[] args) { int page = 1; int maxPage = 1; String header = LanguageUtils.getPlayerLocaleString("geyser.commands.help.header", sender.getLocale(), page, maxPage); 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 f52ab7f36e7..8a000f80cd2 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 @@ -44,7 +44,7 @@ public ListCommand(GeyserConnector connector, String name, String description, S } @Override - public void execute(CommandSender sender, String[] args) { + public void execute(GeyserSession session, CommandSender sender, String[] args) { String message = ""; message = LanguageUtils.getPlayerLocaleString("geyser.commands.list.message", sender.getLocale(), connector.getPlayers().size(), 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 d6916700bd2..4d7d74045ec 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 @@ -45,32 +45,23 @@ public OffhandCommand(GeyserConnector connector, String name, String description } @Override - public void execute(CommandSender sender, String[] args) { - if (sender.isConsole()) { + public void execute(GeyserSession session, CommandSender sender, String[] args) { + if (session == null) { return; } - // Make sure the sender is a Bedrock edition client - if (sender instanceof GeyserSession) { - GeyserSession session = (GeyserSession) sender; - ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0), - BlockFace.DOWN); - session.sendDownstreamPacket(releaseItemPacket); - return; - } - // 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), - BlockFace.DOWN); - session.sendDownstreamPacket(releaseItemPacket); - break; - } - } + ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0), + BlockFace.DOWN); + session.sendDownstreamPacket(releaseItemPacket); } @Override public boolean isExecutableOnConsole() { return false; } + + @Override + public boolean isBedrockOnly() { + return true; + } } 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 798dd7a77b6..2f1c7dc9b2d 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 @@ -34,7 +34,7 @@ public class ReloadCommand extends GeyserCommand { - private GeyserConnector connector; + private final GeyserConnector connector; public ReloadCommand(GeyserConnector connector, String name, String description, String permission) { super(name, description, permission); @@ -42,7 +42,7 @@ public ReloadCommand(GeyserConnector connector, String name, String description, } @Override - public void execute(CommandSender sender, String[] args) { + public void execute(GeyserSession session, CommandSender sender, String[] args) { if (!sender.isConsole() && connector.getPlatformType() == PlatformType.STANDALONE) { return; } @@ -51,8 +51,8 @@ public void execute(CommandSender sender, String[] args) { sender.sendMessage(message); - for (GeyserSession session : connector.getPlayers()) { - session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getLocale())); + for (GeyserSession otherSession : connector.getPlayers()) { + otherSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getLocale())); } connector.reload(); } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/SettingsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/SettingsCommand.java index 5e28e985f49..2aeee137791 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/SettingsCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/SettingsCommand.java @@ -33,30 +33,14 @@ public class SettingsCommand extends GeyserCommand { - private final GeyserConnector connector; - public SettingsCommand(GeyserConnector connector, String name, String description, String permission) { super(name, description, permission); - - this.connector = connector; } @Override - public void execute(CommandSender sender, String[] args) { - // 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; - } - } - } + public void execute(GeyserSession session, CommandSender sender, String[] args) { if (session == null) return; + SettingsUtils.buildForm(session); session.sendForm(session.getSettingsForm(), SettingsUtils.SETTINGS_FORM_ID); } @@ -65,4 +49,9 @@ public void execute(CommandSender sender, String[] args) { public boolean isExecutableOnConsole() { return false; } + + @Override + public boolean isBedrockOnly() { + return true; + } } 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 920ec50c770..3502941d591 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 @@ -34,34 +34,14 @@ 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; - } - } - } + public void execute(GeyserSession session, CommandSender sender, String[] args) { if (session == null) return; + session.setWaitingForStatistics(true); ClientRequestPacket clientRequestPacket = new ClientRequestPacket(ClientRequest.STATS); session.sendDownstreamPacket(clientRequestPacket); @@ -71,4 +51,9 @@ public void execute(CommandSender sender, String[] args) { public boolean isExecutableOnConsole() { return false; } + + @Override + public boolean isBedrockOnly() { + return true; + } } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java index b192c9e9a24..b00e44b729a 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java @@ -29,12 +29,13 @@ 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 java.util.Collections; public class StopCommand extends GeyserCommand { - private GeyserConnector connector; + private final GeyserConnector connector; public StopCommand(GeyserConnector connector, String name, String description, String permission) { super(name, description, permission); @@ -44,7 +45,7 @@ public StopCommand(GeyserConnector connector, String name, String description, S } @Override - public void execute(CommandSender sender, String[] args) { + public void execute(GeyserSession session, CommandSender sender, String[] args) { if (!sender.isConsole() && connector.getPlatformType() == PlatformType.STANDALONE) { return; } 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 e0c445b3f2f..226a770a6fe 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 @@ -32,6 +32,7 @@ import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.network.BedrockProtocol; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.WebUtils; @@ -44,15 +45,12 @@ public class VersionCommand extends GeyserCommand { - public GeyserConnector connector; - public VersionCommand(GeyserConnector connector, String name, String description, String permission) { super(name, description, permission); - this.connector = connector; } @Override - public void execute(CommandSender sender, String[] args) { + public void execute(GeyserSession session, CommandSender sender, String[] args) { String bedrockVersions; List supportedCodecs = BedrockProtocol.SUPPORTED_BEDROCK_CODECS; if (supportedCodecs.size() > 1) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java index 6ff29f5cc18..a9ed15cefa3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java @@ -43,7 +43,7 @@ public class BedrockCommandRequestTranslator extends PacketTranslator Date: Thu, 21 Jan 2021 17:54:18 -0500 Subject: [PATCH 28/37] Localize Done in the OAuth prompt (#1851) Oops. --- .../java/org/geysermc/connector/utils/LoginEncryptionUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fd7ef4e6405..3c093df94cf 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -203,7 +203,7 @@ public static void showMicrosoftAuthenticationWindow(GeyserSession session) { */ public static void showMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse response) { ModalFormWindow msaCodeWindow = new ModalFormWindow("%xbox.signin", "%xbox.signin.website\n%xbox.signin.url\n%xbox.signin.enterCode\n" + - response.user_code, "Done", "%menu.disconnect"); + response.user_code, "%gui.done", "%menu.disconnect"); session.sendForm(msaCodeWindow, LoginEncryptionUtils.AUTH_MSA_CODE_FORM_ID); } From 0b9b3eb127519466daff80fe1e283d90442b148b Mon Sep 17 00:00:00 2001 From: ScxLore1216 <30559794+EddieDiamondFire@users.noreply.github.com> Date: Fri, 22 Jan 2021 10:00:18 +1100 Subject: [PATCH 29/37] Update bungeecord-api to 1.16-R0.4-SNAPSHOT (#1857) --- bootstrap/bungeecord/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 54e0d56ef21..10855aed411 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -20,7 +20,7 @@ net.md-5 bungeecord-api - 1.15-SNAPSHOT + 1.16-R0.4-SNAPSHOT provided From 5a8604fe54d9391cac0288d8738b10cf31605884 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 21 Jan 2021 19:03:46 -0500 Subject: [PATCH 30/37] Skin fixes and optimizations (#1856) - Fix self-assigned player skins getting overwritten - Fix players with no skin silently throwing an exception, and properly handle it instead - CRITICAL bug fix of handling Deadmau5's skin - it's not handled by his UUID but by his username --- .../network/session/cache/EntityCache.java | 4 +- .../player/JavaPlayerListEntryTranslator.java | 42 ++++++------ .../geysermc/connector/skin/SkinManager.java | 68 ++++++++++++------- 3 files changed, 67 insertions(+), 47 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java index 40000551c42..a2eb6005394 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java @@ -128,8 +128,8 @@ public PlayerEntity getPlayerEntity(UUID uuid) { return playerEntities.get(uuid); } - public void removePlayerEntity(UUID uuid) { - playerEntities.remove(uuid); + public PlayerEntity removePlayerEntity(UUID uuid) { + return playerEntities.remove(uuid); } public void addBossBar(UUID uuid, BossBar bossBar) { 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 cd627c645e2..b8a7972c877 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 @@ -25,6 +25,11 @@ package org.geysermc.connector.network.translators.java.entity.player; +import com.github.steveice10.mc.protocol.data.game.PlayerListEntry; +import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPlayerListEntryPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.entity.player.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; @@ -32,12 +37,6 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.skin.SkinManager; -import com.github.steveice10.mc.protocol.data.game.PlayerListEntry; -import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPlayerListEntryPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; - @Translator(packet = ServerPlayerListEntryPacket.class) public class JavaPlayerListEntryTranslator extends PacketTranslator { @Override @@ -57,9 +56,6 @@ public void translate(ServerPlayerListEntryPacket packet, GeyserSession session) if (self) { // Entity is ourself playerEntity = session.getPlayerEntity(); - //TODO: playerEntity.setProfile(entry.getProfile()); seems to help with online mode skins but needs more testing to ensure Floodgate skins aren't overwritten - SkinManager.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape -> - GeyserConnector.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data")); } else { playerEntity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId()); } @@ -74,27 +70,35 @@ public void translate(ServerPlayerListEntryPacket packet, GeyserSession session) Vector3f.ZERO, Vector3f.ZERO ); - } - session.getEntityCache().addPlayerEntity(playerEntity); + session.getEntityCache().addPlayerEntity(playerEntity); + } else { + playerEntity.setProfile(entry.getProfile()); + } - playerEntity.setProfile(entry.getProfile()); playerEntity.setPlayerList(true); - playerEntity.setValid(true); - PlayerListPacket.Entry playerListEntry = SkinManager.buildCachedEntry(session, playerEntity); + // We'll send our own PlayerListEntry in requestAndHandleSkinAndCape + // But we need to send other player's entries so they show up in the player list + // without processing their skin information - that'll be processed when they spawn in + if (self) { + SkinManager.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape -> + GeyserConnector.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data for " + session.getClientData().getUsername())); + } else { + playerEntity.setValid(true); + PlayerListPacket.Entry playerListEntry = SkinManager.buildCachedEntry(session, playerEntity); - translate.getEntries().add(playerListEntry); + translate.getEntries().add(playerListEntry); + } break; case REMOVE_PLAYER: - PlayerEntity entity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId()); + // As the player entity is no longer present, we can remove the entry + PlayerEntity entity = session.getEntityCache().removePlayerEntity(entry.getProfile().getId()); if (entity != null) { // Just remove the entity's player list status // Don't despawn the entity - the Java server will also take care of that. entity.setPlayerList(false); } - // As the player entity is no longer present, we can remove the entry - session.getEntityCache().removePlayerEntity(entry.getProfile().getId()); if (entity == session.getPlayerEntity()) { // If removing ourself we use our AuthData UUID translate.getEntries().add(new PlayerListPacket.Entry(session.getAuthData().getUUID())); @@ -105,7 +109,7 @@ public void translate(ServerPlayerListEntryPacket packet, GeyserSession session) } } - if (packet.getAction() == PlayerListEntryAction.REMOVE_PLAYER || session.getUpstream().isInitialized()) { + if (!translate.getEntries().isEmpty() && (packet.getAction() == PlayerListEntryAction.REMOVE_PLAYER || session.getUpstream().isInitialized())) { session.sendUpstreamPacket(translate); } } diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java b/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java index b39e7f35202..ae3abc9434c 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java +++ b/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java @@ -33,7 +33,6 @@ import lombok.AllArgsConstructor; import lombok.Getter; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.player.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.auth.BedrockClientData; @@ -47,6 +46,9 @@ public class SkinManager { + /** + * Builds a Bedrock player list entry from our existing, cached Bedrock skin information + */ public static PlayerListPacket.Entry buildCachedEntry(GeyserSession session, PlayerEntity playerEntity) { GameProfileData data = GameProfileData.from(playerEntity.getProfile()); SkinProvider.Cape cape = SkinProvider.getCachedCape(data.getCapeUrl()); @@ -70,27 +72,31 @@ public static PlayerListPacket.Entry buildCachedEntry(GeyserSession session, Pla ); } + /** + * With all the information needed, build a Bedrock player entry with translated skin information. + */ public static PlayerListPacket.Entry buildEntryManually(GeyserSession session, UUID uuid, String username, long geyserId, String skinId, byte[] skinData, String capeId, byte[] capeData, SkinProvider.SkinGeometry geometry) { SerializedSkin serializedSkin = SerializedSkin.of( skinId, geometry.getGeometryName(), ImageData.of(skinData), Collections.emptyList(), - ImageData.of(capeData), geometry.getGeometryData(), "", true, false, !capeId.equals(SkinProvider.EMPTY_CAPE.getCapeId()), capeId, skinId + ImageData.of(capeData), geometry.getGeometryData(), "", true, false, + !capeId.equals(SkinProvider.EMPTY_CAPE.getCapeId()), capeId, skinId ); - // This attempts to find the xuid of the player so profile images show up for xbox accounts + // This attempts to find the XUID of the player so profile images show up for Xbox accounts String xuid = ""; - GeyserSession player = GeyserConnector.getInstance().getPlayerByUuid(uuid); + GeyserSession playerSession = GeyserConnector.getInstance().getPlayerByUuid(uuid); - if (player != null) { - xuid = player.getAuthData().getXboxUUID(); + if (playerSession != null) { + xuid = playerSession.getAuthData().getXboxUUID(); } PlayerListPacket.Entry entry; // If we are building a PlayerListEntry for our own session we use our AuthData UUID instead of the Java UUID - // as bedrock expects to get back its own provided uuid + // as Bedrock expects to get back its own provided UUID if (session.getPlayerEntity().getUuid().equals(uuid)) { entry = new PlayerListPacket.Entry(session.getAuthData().getUUID()); } else { @@ -134,12 +140,13 @@ public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSessio geometry, entity.getUuid() ), geometry, 3); + boolean isDeadmau5 = "deadmau5".equals(entity.getUsername()); // Not a bedrock player check for ears - if (geometry.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_EARS) { + if (geometry.isFailed() && (SkinProvider.ALLOW_THIRD_PARTY_EARS || isDeadmau5)) { boolean isEars; // Its deadmau5, gotta support his skin :) - if (entity.getUuid().toString().equals("1e18d5ff-643d-45c8-b509-43b8461d8614")) { + if (isDeadmau5) { isEars = true; } else { // Get the ears texture for the player @@ -185,7 +192,6 @@ public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSessio playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE); playerRemovePacket.getEntries().add(updatedEntry); session.sendUpstreamPacket(playerRemovePacket); - } } } catch (Exception e) { @@ -238,20 +244,20 @@ public static class GameProfileData { * @return The built GameProfileData */ public static GameProfileData from(GameProfile profile) { - // Fallback to the offline mode of working it out - boolean isAlex = (Math.abs(profile.getId().hashCode() % 2) == 1); - try { GameProfile.Property skinProperty = profile.getProperty("textures"); - // TODO: Remove try/catch here + if (skinProperty == null) { + // Likely offline mode + return loadBedrockOrOfflineSkin(profile); + } JsonNode skinObject = GeyserConnector.JSON_MAPPER.readTree(new String(Base64.getDecoder().decode(skinProperty.getValue()), StandardCharsets.UTF_8)); JsonNode textures = skinObject.get("textures"); JsonNode skinTexture = textures.get("SKIN"); String skinUrl = skinTexture.get("url").asText().replace("http://", "https://"); - isAlex = skinTexture.has("metadata"); + boolean isAlex = skinTexture.has("metadata"); String capeUrl = null; if (textures.has("CAPE")) { @@ -261,20 +267,30 @@ public static GameProfileData from(GameProfile profile) { return new GameProfileData(skinUrl, capeUrl, isAlex); } catch (Exception exception) { - if (GeyserConnector.getInstance().getAuthType() != AuthType.OFFLINE) { - GeyserConnector.getInstance().getLogger().debug("Got invalid texture data for " + profile.getName() + " " + exception.getMessage()); - } - // return default skin with default cape when texture data is invalid - String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl(); - if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) { - GeyserSession session = GeyserConnector.getInstance().getPlayerByUuid(profile.getId()); + GeyserConnector.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName() + ": " + exception.getMessage()); + return loadBedrockOrOfflineSkin(profile); + } + } - if (session != null) { - skinUrl = session.getClientData().getSkinId(); - } + /** + * @return default skin with default cape when texture data is invalid, or the Bedrock player's skin if this + * is a Bedrock player. + */ + private static GameProfileData loadBedrockOrOfflineSkin(GameProfile profile) { + // Fallback to the offline mode of working it out + boolean isAlex = (Math.abs(profile.getId().hashCode() % 2) == 1); + + String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl(); + String capeUrl = SkinProvider.EMPTY_CAPE.getTextureUrl(); + if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) { + GeyserSession session = GeyserConnector.getInstance().getPlayerByUuid(profile.getId()); + + if (session != null) { + skinUrl = session.getClientData().getSkinId(); + capeUrl = session.getClientData().getCapeId(); } - return new GameProfileData(skinUrl, SkinProvider.EMPTY_CAPE.getTextureUrl(), isAlex); } + return new GameProfileData(skinUrl, capeUrl, isAlex); } } } From f56663456df254c40cca8cbff50849dd7ef6dab1 Mon Sep 17 00:00:00 2001 From: YHDiamond <47502993+YHDiamond@users.noreply.github.com> Date: Wed, 27 Jan 2021 19:20:27 -0500 Subject: [PATCH 31/37] Update CONTRIBUTING.md (#1787) * Update CONTRIBUTING.md * Update CONTRIBUTING.md Ok that works to but i meant for that to mean 'put all your annotation stuff here' obviously it wont compile, but this works too Co-authored-by: theminecoder Co-authored-by: YHDiamond <47502993+yehudahrrs@users.noreply.github.com> Co-authored-by: theminecoder --- CONTRIBUTING.md | 53 ++++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c7a5a3d28be..527471bae77 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,29 +11,36 @@ Thank for for considering a contribution! Generally, Geyser welcomes PRs from ev We have some general style guides that should be applied throughout the code: ```java - -private static final AIR_ITEM = 0; // Static item names should be capitalized - -public Int2IntMap items = new Int2IntOpenHashMap(); // Use the interface as the class type but initialize with the implementation. - -public int nameWithMultipleWords = 0; - -/** -* Javadoc comment to explain what a function does. -*/ -public void applyStuff() { - if (condition) { - // Do stuff. - } else if (anotherCondition) { - // Do something else. - } - - switch (value) { - case 0: - break; - case 1: - break: - } +public class LongClassName { + private static final int AIR_ITEM = 0; // Static item names should be capitalized + + public Int2IntMap items = new Int2IntOpenHashMap(); // Use the interface as the class type but initialize with the implementation. + + public int nameWithMultipleWords = 0; + + /** + * Javadoc comment to explain what a function does. + */ + @RandomAnnotation(stuff = true, moreStuff = "might exist") + public void applyStuff() { + Variable variable = new Variable(); + Variable otherVariable = new Variable(); + + if (condition) { + // Do stuff. + } else if (anotherCondition) { + // Do something else. + } + + switch (value) { + case 0: + stuff(); + break; + case 1: + differentStuff(); + break; + } + } } ``` From ce6055460eb786bc90072daa2a2070f7a8402736 Mon Sep 17 00:00:00 2001 From: 7man7LMYT <67489949+7man7LMYT@users.noreply.github.com> Date: Wed, 27 Jan 2021 16:21:47 -0800 Subject: [PATCH 32/37] Bedrock doesn't support custom armor stand poses (#1873) Added the line that states that custom armor stand poses aren't possible in bedrock. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 68f98c01872..343fca94a12 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ The following things can't be fixed because of Bedrock limitations. They might b - Custom heads in inventories - Clickable links in chat - Glowing effect +- Custom armor stand poses ## Compiling 1. Clone the repo to your computer From aec08e7d3cc085e10678b2dff50635246cf1b256 Mon Sep 17 00:00:00 2001 From: YHDiamond <47502993+YHDiamond@users.noreply.github.com> Date: Wed, 27 Jan 2021 19:23:27 -0500 Subject: [PATCH 33/37] Add images to statistics menu (#1719) Co-authored-by: YHDiamond <47502993+yehudahrrs@users.noreply.github.com> --- .../connector/utils/StatisticsUtils.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java index 7d2a75fc812..08521284baa 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java @@ -30,6 +30,7 @@ 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.button.FormImage; import org.geysermc.common.window.response.SimpleFormResponse; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemRegistry; @@ -54,17 +55,17 @@ public static SimpleFormWindow buildMenuForm(GeyserSession session) { 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.generalButton", language), new FormImage(FormImage.FormImageType.PATH, "textures/ui/World"))); - 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.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language), new FormImage(FormImage.FormImageType.PATH, "textures/items/iron_pickaxe"))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language), new FormImage(FormImage.FormImageType.PATH, "textures/items/record_11"))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language), new FormImage(FormImage.FormImageType.PATH, "textures/blocks/crafting_table_side"))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language), new FormImage(FormImage.FormImageType.PATH, "textures/ui/Wrenches1"))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language), new FormImage(FormImage.FormImageType.PATH, "textures/blocks/chest_front"))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language), new FormImage(FormImage.FormImageType.PATH, "textures/ui/trash_default"))); - 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))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language), new FormImage(FormImage.FormImageType.PATH, "textures/items/diamond_sword"))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language), new FormImage(FormImage.FormImageType.PATH, "textures/ui/wither_heart_flash"))); return window; } @@ -189,7 +190,7 @@ public static boolean handleMenuForm(GeyserSession session, String response) { } SimpleFormWindow window = new SimpleFormWindow(title, content.toString()); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("gui.back", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("gui.back", language), new FormImage(FormImage.FormImageType.PATH, "textures/gui/newgui/undo"))); session.sendForm(window, STATISTICS_LIST_FORM_ID); } From 462e9b8b0d110dd80cadf0cd0124bc3ebcf054f4 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Thu, 28 Jan 2021 16:08:20 +0000 Subject: [PATCH 34/37] Fix beehive direction being flipped (#1880) * Fix beehive direction being flipped * Update submodule --- connector/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index dd0347bd51e..c4f07d67244 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit dd0347bd51e00e42ea58faaf68b562526c4d2817 +Subproject commit c4f07d67244b2746d0a8b2d94cd10cb2833d4018 From 498f7653d3ed4334733b48bee203defd33b6b2b1 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Thu, 28 Jan 2021 16:33:26 +0000 Subject: [PATCH 35/37] Fix NPE when link entity not found and use non-deprecated constructor (#1706) * Fix NPE when link entity not found and use non-deprecated constructor * Extract method call to variable * Update PlayerEntity.java * Fix indentation --- .../org/geysermc/connector/entity/player/PlayerEntity.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java index fc7b867bb73..5cef3252ad0 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java @@ -103,7 +103,10 @@ public void spawnEntity(GeyserSession session) { long linkedEntityId = session.getEntityCache().getCachedPlayerEntityLink(entityId); if (linkedEntityId != -1) { - addPlayerPacket.getEntityLinks().add(new EntityLinkData(session.getEntityCache().getEntityByJavaId(linkedEntityId).getGeyserId(), geyserId, EntityLinkData.Type.RIDER, false)); + Entity linkedEntity = session.getEntityCache().getEntityByJavaId(linkedEntityId); + if (linkedEntity != null) { + addPlayerPacket.getEntityLinks().add(new EntityLinkData(linkedEntity.getGeyserId(), geyserId, EntityLinkData.Type.RIDER, false, false)); + } } valid = true; From a0db56ba8727ad58e2fb94dc3f1f445b5f69badf Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 31 Jan 2021 14:22:26 -0500 Subject: [PATCH 36/37] Fix lightning bolt sound (#1889) --- connector/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index c4f07d67244..2e52b01cc54 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit c4f07d67244b2746d0a8b2d94cd10cb2833d4018 +Subproject commit 2e52b01cc541c8925346f93be8940087d9af1661 From cf149b58e07d80e49dd7b9b8cd974e0d27304cad Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 2 Feb 2021 00:46:46 +0100 Subject: [PATCH 37/37] Fixed remaining merge conflicts --- .../connector/command/CommandManager.java | 2 +- .../command/defaults/AdvancementsCommand.java | 13 +- .../network/session/GeyserSession.java | 4 +- .../session/cache/AdvancementsCache.java | 261 ++++++++---------- .../java/JavaAdvancementsTabTranslator.java | 7 +- .../connector/utils/LoginEncryptionUtils.java | 7 +- 6 files changed, 120 insertions(+), 174 deletions(-) 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 9f675ae81ed..71eb2c742f0 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -53,7 +53,7 @@ public CommandManager(GeyserConnector connector) { registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version")); registerCommand(new SettingsCommand(connector, "settings", "geyser.commands.settings.desc", "geyser.command.settings")); registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); - registerCommand(new AdvancementsCommand(connector, "advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); + registerCommand(new AdvancementsCommand( "advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); } public void registerCommand(GeyserCommand command) { diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java index 2ef23381b99..8ace83840fd 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java @@ -25,25 +25,20 @@ package org.geysermc.connector.command.defaults; -import org.geysermc.common.window.SimpleFormWindow; -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.network.session.cache.AdvancementsCache; public class AdvancementsCommand extends GeyserCommand { - - public AdvancementsCommand(GeyserConnector connector, String name, String description, String permission) { + public AdvancementsCommand(String name, String description, String permission) { super(name, description, permission); } @Override public void execute(GeyserSession session, CommandSender sender, String[] args) { - if (session == null) return; - - SimpleFormWindow window = session.getAdvancementsCache().buildMenuForm(); - session.sendForm(window, AdvancementsCache.ADVANCEMENTS_MENU_FORM_ID); + if (session != null) { + session.getAdvancementsCache().buildAndShowMenuForm(); + } } @Override 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 acfd8ae2160..8bdb7f2b156 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 @@ -489,7 +489,7 @@ public void authenticateWithMicrosoftCode() { MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID); MsaAuthenticationService.MsCodeResponse response = msaAuthenticationService.getAuthCode(); - LoginEncryptionUtils.showMicrosoftCodeWindow(this, response); + LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response); // This just looks cool SetTimePacket packet = new SetTimePacket(); @@ -605,7 +605,7 @@ public void connected(ConnectedEvent event) { // Let the user know there locale may take some time to download // as it has to be extracted from a JAR - if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) { + if (locale.equalsIgnoreCase("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) { // This should probably be left hardcoded as it will only show for en_us clients sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time"); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java index 369967accdb..d20eb11dd64 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java @@ -29,26 +29,20 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientAdvancementTabPacket; import lombok.Getter; import lombok.Setter; -import org.geysermc.common.window.SimpleFormWindow; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.response.SimpleFormResponse; +import lombok.experimental.Accessors; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.utils.GeyserAdvancement; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LocaleUtils; +import org.geysermc.cumulus.SimpleForm; +import org.geysermc.cumulus.response.SimpleFormResponse; import java.util.HashMap; import java.util.List; import java.util.Map; public class AdvancementsCache { - - // Different form IDs - public static final int ADVANCEMENTS_MENU_FORM_ID = 1341; - public static final int ADVANCEMENTS_LIST_FORM_ID = 1342; - public static final int ADVANCEMENT_INFO_FORM_ID = 1343; - /** * Stores the player's advancement progress */ @@ -64,7 +58,7 @@ public class AdvancementsCache { /** * Stores player's chosen advancement's ID and title for use in form creators. */ - @Setter + @Setter @Accessors(chain = true) private String currentAdvancementCategoryId = null; private final GeyserSession session; @@ -74,73 +68,128 @@ public AdvancementsCache(GeyserSession session) { } /** - * Build a form with all advancement categories - * - * @return The built advancement category menu + * Build and send a form with all advancement categories */ - public SimpleFormWindow buildMenuForm() { - // Cache the language for cleaner access - String language = session.getClientData().getLanguageCode(); + public void buildAndShowMenuForm() { + SimpleForm.Builder builder = + SimpleForm.builder() + .translator(LocaleUtils::getLocaleString, session.getLocale()) + .title("gui.advancements"); - // Created menu window for advancement categories - SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.advancements", language), ""); + boolean hasAdvancements = false; for (Map.Entry advancement : storedAdvancements.entrySet()) { if (advancement.getValue().getParentId() == null) { // No parent means this is a root advancement - window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), language))); + hasAdvancements = true; + builder.button(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), session.getLocale())); } } - if (window.getButtons().isEmpty()) { - window.setContent(LocaleUtils.getLocaleString("advancements.empty", language)); + if (!hasAdvancements) { + builder.content("advancements.empty"); } - return window; + builder.responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + return; + } + + String id = ""; + + int advancementIndex = 0; + for (Map.Entry advancement : storedAdvancements.entrySet()) { + if (advancement.getValue().getParentId() == null) { // Root advancement + if (advancementIndex == response.getClickedButtonId()) { + id = advancement.getKey(); + break; + } else { + advancementIndex++; + } + } + } + + if (!id.equals("")) { + if (id.equals(currentAdvancementCategoryId)) { + // The server thinks we are already on this tab + buildAndShowListForm(); + } else { + // Send a packet indicating that we intend to open this particular advancement window + ClientAdvancementTabPacket packet = new ClientAdvancementTabPacket(id); + session.sendDownstreamPacket(packet); + // Wait for a response there + } + } + }); + + session.sendForm(builder); } /** - * Builds the list of advancements - * - * @return The built list form + * Build and send the list of advancements */ - public SimpleFormWindow buildListForm() { - // Cache the language for easier access - String language = session.getLocale(); - String id = currentAdvancementCategoryId; + public void buildAndShowListForm() { GeyserAdvancement categoryAdvancement = storedAdvancements.get(currentAdvancementCategoryId); + String language = session.getLocale(); - // Create the window - SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language), - MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language)); + SimpleForm.Builder builder = + SimpleForm.builder() + .title(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language)) + .content(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language)); - if (id != null) { - for (Map.Entry advancementEntry : storedAdvancements.entrySet()) { - GeyserAdvancement advancement = advancementEntry.getValue(); + if (currentAdvancementCategoryId != null) { + for (GeyserAdvancement advancement : storedAdvancements.values()) { if (advancement != null) { if (advancement.getParentId() != null && currentAdvancementCategoryId.equals(advancement.getRootId(this))) { - boolean earned = isEarned(advancement); - - if (earned || !advancement.getDisplayData().isShowToast()) { - window.getButtons().add(new FormButton("§6" + MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n")); - } else { - window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n")); - } + boolean color = isEarned(advancement) || !advancement.getDisplayData().isShowToast(); + builder.button((color ? "§6" : "") + MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()) + '\n'); } } } } - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language))); + builder.button(LanguageUtils.getPlayerLocaleString("gui.back", language)); - return window; + builder.responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + // Indicate that we have closed the current advancement tab + session.sendDownstreamPacket(new ClientAdvancementTabPacket()); + return; + } + + GeyserAdvancement advancement = null; + int advancementIndex = 0; + // Loop around to find the advancement that the client pressed + for (GeyserAdvancement advancementEntry : storedAdvancements.values()) { + if (advancementEntry.getParentId() != null && + currentAdvancementCategoryId.equals(advancementEntry.getRootId(this))) { + if (advancementIndex == response.getClickedButtonId()) { + advancement = advancementEntry; + break; + } else { + advancementIndex++; + } + } + } + + if (advancement != null) { + buildAndShowInfoForm(advancement); + } else { + buildAndShowMenuForm(); + // Indicate that we have closed the current advancement tab + session.sendDownstreamPacket(new ClientAdvancementTabPacket()); + } + }); + + session.sendForm(builder); } /** * Builds the advancement display info based on the chosen category * * @param advancement The advancement used to create the info display - * @return The information for the chosen advancement */ - public SimpleFormWindow buildInfoForm(GeyserAdvancement advancement) { + public void buildAndShowInfoForm(GeyserAdvancement advancement) { // Cache language for easier access String language = session.getLocale(); @@ -160,16 +209,24 @@ public SimpleFormWindow buildInfoForm(GeyserAdvancement advancement) { Parent Advancement: Minecraft // If relevant */ - String content = description + "\n\n§f" + - earnedString + "\n"; + String content = description + "\n\n§f" + earnedString + "\n"; if (!currentAdvancementCategoryId.equals(advancement.getParentId())) { // Only display the parent if it is not the category content += LanguageUtils.getPlayerLocaleString("geyser.advancements.parentid", language, MessageTranslator.convertMessage(storedAdvancements.get(advancement.getParentId()).getDisplayData().getTitle(), language)); } - SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()), content); - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language))); - return window; + session.sendForm( + SimpleForm.builder() + .title(MessageTranslator.convertMessage(advancement.getDisplayData().getTitle())) + .content(content) + .button(LanguageUtils.getPlayerLocaleString("gui.back", language)) + .responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (response.isCorrect()) { + buildAndShowListForm(); + } + }) + ); } /** @@ -209,108 +266,6 @@ public boolean isEarned(GeyserAdvancement advancement) { return earned; } - /** - * Handle the menu form response - * - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public boolean handleMenuForm(String response) { - SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_MENU_FORM_ID); - menuForm.setResponse(response); - - SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse(); - - String id = ""; - if (formResponse != null && formResponse.getClickedButton() != null) { - int advancementIndex = 0; - for (Map.Entry advancement : storedAdvancements.entrySet()) { - if (advancement.getValue().getParentId() == null) { // Root advancement - if (advancementIndex == formResponse.getClickedButtonId()) { - id = advancement.getKey(); - break; - } else { - advancementIndex++; - } - } - } - } - if (!id.equals("")) { - if (id.equals(currentAdvancementCategoryId)) { - // The server thinks we are already on this tab - session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID); - } else { - // Send a packet indicating that we intend to open this particular advancement window - ClientAdvancementTabPacket packet = new ClientAdvancementTabPacket(id); - session.sendDownstreamPacket(packet); - // Wait for a response there - } - } - - return true; - } - - /** - * Handle the list form response (Advancement category choice) - * - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public boolean handleListForm(String response) { - SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_LIST_FORM_ID); - listForm.setResponse(response); - - SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse(); - - if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) { - GeyserAdvancement advancement = null; - int advancementIndex = 0; - // Loop around to find the advancement that the client pressed - for (GeyserAdvancement advancementEntry : storedAdvancements.values()) { - if (advancementEntry.getParentId() != null && - currentAdvancementCategoryId.equals(advancementEntry.getRootId(this))) { - if (advancementIndex == formResponse.getClickedButtonId()) { - advancement = advancementEntry; - break; - } else { - advancementIndex++; - } - } - } - if (advancement != null) { - session.sendForm(buildInfoForm(advancement), ADVANCEMENT_INFO_FORM_ID); - } else { - session.sendForm(buildMenuForm(), ADVANCEMENTS_MENU_FORM_ID); - // Indicate that we have closed the current advancement tab - session.sendDownstreamPacket(new ClientAdvancementTabPacket()); - } - } else { - // Indicate that we have closed the current advancement tab - session.sendDownstreamPacket(new ClientAdvancementTabPacket()); - } - - return true; - } - - /** - * Handle the info form response - * - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public boolean handleInfoForm(String response) { - SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENT_INFO_FORM_ID); - listForm.setResponse(response); - - SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse(); - - if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) { - session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID); - } - - return true; - } - public String getColorFromAdvancementFrameType(GeyserAdvancement advancement) { String base = "\u00a7"; if (advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java index 17a3b3792e9..80b9f915564 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java @@ -27,7 +27,6 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.ServerAdvancementTabPacket; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.AdvancementsCache; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -36,10 +35,10 @@ */ @Translator(packet = ServerAdvancementTabPacket.class) public class JavaAdvancementsTabTranslator extends PacketTranslator { - @Override public void translate(ServerAdvancementTabPacket packet, GeyserSession session) { - session.getAdvancementsCache().setCurrentAdvancementCategoryId(packet.getTabId()); - session.sendForm(session.getAdvancementsCache().buildListForm(), AdvancementsCache.ADVANCEMENTS_LIST_FORM_ID); + session.getAdvancementsCache() + .setCurrentAdvancementCategoryId(packet.getTabId()) + .buildAndShowListForm(); } } 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 e330cbf705f..00c7aea0f83 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -169,7 +169,7 @@ public static void buildAndShowLoginWindow(GeyserSession session) { .translator(LanguageUtils::getPlayerLocaleString, session.getLocale()) .title("geyser.auth.login.form.notice.title") .content("geyser.auth.login.form.notice.desc") - .button("geyser.auth.login.form.notice.btn_login.mojang") //todo optional + .optionalButton("geyser.auth.login.form.notice.btn_login.mojang", isPasswordAuthEnabled) .button("geyser.auth.login.form.notice.btn_login.microsoft") .button("geyser.auth.login.form.notice.btn_disconnect") .responseHandler((form, responseData) -> { @@ -179,9 +179,6 @@ public static void buildAndShowLoginWindow(GeyserSession session) { return; } - int microsoftButton = isPasswordAuthentication ? 1 : 0; - int disconnectButton = isPasswordAuthentication ? 2 : 1; - if (isPasswordAuthEnabled && response.getClickedButtonId() == 0) { session.setMicrosoftAccount(false); buildAndShowLoginDetailsWindow(session); @@ -272,6 +269,6 @@ public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, MsaAut session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); } }) - ) + ); } }