diff --git a/README.md b/README.md index 9878fa5a04c..b8f8749b8b6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Geyser +Geyser [![forthebadge made-with-java](http://ForTheBadge.com/images/badges/made-with-java.svg)](https://java.com/) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java index 24793839d3e..1cf519c0e1d 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java @@ -44,6 +44,7 @@ import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; +import java.nio.file.Path; import java.util.UUID; import java.util.logging.Level; @@ -134,4 +135,9 @@ public CommandManager getGeyserCommandManager() { public IGeyserPingPassthrough getGeyserPingPassthrough() { return geyserBungeePingPassthrough; } + + @Override + public Path getConfigFolder() { + return getDataFolder().toPath(); + } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index d8f44f36212..b5e2264a2f6 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -45,6 +45,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.util.UUID; import java.util.logging.Level; @@ -162,6 +163,11 @@ public WorldManager getWorldManager() { return this.geyserWorldManager; } + @Override + public Path getConfigFolder() { + return getDataFolder().toPath(); + } + public boolean isCompatible(String version, String whichVersion) { int[] currentVersion = parseVersion(version); int[] otherVersion = parseVersion(whichVersion); @@ -195,4 +201,5 @@ private int[] parseVersion(String versionParam) { } return temp; } + } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java index 2b02ec0d42f..79e7621cca1 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java @@ -129,6 +129,11 @@ public boolean isAllowThirdPartyEars() { return node.getNode("allow-third-party-ears").getBoolean(false); } + @Override + public boolean isShowCooldown() { + return node.getNode("show-cooldown").getBoolean(true); + } + @Override public String getDefaultLocale() { return node.getNode("default-locale").getString("en_us"); diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java index 2288fc67474..c5f13b58199 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java @@ -50,6 +50,7 @@ import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; +import java.nio.file.Path; import java.util.UUID; @Plugin(id = "geyser", name = GeyserConnector.NAME + "-Sponge", version = GeyserConnector.VERSION, url = "https://geysermc.org", authors = "GeyserMC") @@ -147,6 +148,11 @@ public IGeyserPingPassthrough getGeyserPingPassthrough() { return geyserSpongePingPassthrough; } + @Override + public Path getConfigFolder() { + return configDir.toPath(); + } + @Listener public void onServerStart(GameStartedServerEvent event) { onEnable(); diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java index a1c47238786..0fca3503831 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java @@ -37,6 +37,8 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.UUID; public class GeyserStandaloneBootstrap implements GeyserBootstrap { @@ -100,4 +102,10 @@ public CommandManager getGeyserCommandManager() { public IGeyserPingPassthrough getGeyserPingPassthrough() { return geyserPingPassthrough; } + + @Override + public Path getConfigFolder() { + // Return the current working directory + return Paths.get(System.getProperty("user.dir")); + } } diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java index ffb252b2ef9..7102d206ed3 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java @@ -82,7 +82,7 @@ public void warning(String message) { @Override public void info(String message) { - log.info(printConsole(ChatColor.WHITE + message, colored)); + log.info(printConsole(ChatColor.RESET + ChatColor.BOLD + message, colored)); } @Override diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java index e2a4787b4f0..5abf3c23098 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java @@ -34,6 +34,7 @@ import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.proxy.ProxyServer; +import lombok.Getter; import org.geysermc.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.GeyserConnector; @@ -48,6 +49,8 @@ import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.UUID; @Plugin(id = "geyser", name = GeyserConnector.NAME + "-Velocity", version = GeyserConnector.VERSION, url = "https://geysermc.org", authors = "GeyserMC") @@ -69,14 +72,16 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { private GeyserConnector connector; + @Getter + private final Path configFolder = Paths.get("plugins/" + GeyserConnector.NAME + "-Velocity/"); + @Override public void onEnable() { - File configDir = new File("plugins/" + GeyserConnector.NAME + "-Velocity/"); - try { - if (!configDir.exists()) - configDir.mkdir(); - File configFile = FileUtils.fileOrCopiedFromResource(new File(configDir, "config.yml"), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); + if (!configFolder.toFile().exists()) + //noinspection ResultOfMethodCallIgnored + configFolder.toFile().mkdirs(); + File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class); } catch (IOException ex) { logger.warn("Failed to read/create config.yml! Make sure it's up to date and/or readable+writable!", ex); @@ -101,7 +106,7 @@ public void onEnable() { return; } - geyserConfig.loadFloodgate(this, proxyServer, configDir); + geyserConfig.loadFloodgate(this, proxyServer, configFolder.toFile()); this.connector = GeyserConnector.start(PlatformType.VELOCITY, this); diff --git a/common/src/main/java/org/geysermc/common/window/button/FormImage.java b/common/src/main/java/org/geysermc/common/window/button/FormImage.java index b700b046b1c..72579f7acfc 100644 --- a/common/src/main/java/org/geysermc/common/window/button/FormImage.java +++ b/common/src/main/java/org/geysermc/common/window/button/FormImage.java @@ -32,14 +32,14 @@ public class FormImage { @Getter @Setter - private FormImageType type; + private String type; @Getter @Setter private String data; public FormImage(FormImageType type, String data) { - this.type = type; + this.type = type.getName(); this.data = data; } diff --git a/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java b/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java index 50a5c631a9c..f972d590685 100644 --- a/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java +++ b/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java @@ -25,9 +25,17 @@ package org.geysermc.common.window.component; +import lombok.Getter; +import lombok.Setter; + public class ToggleComponent extends FormComponent { + @Getter + @Setter private String text; + + @Getter + @Setter private boolean defaultValue; public ToggleComponent(String text) { diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index abd48bc29e7..d201656ad25 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -31,6 +31,7 @@ import com.nukkitx.protocol.bedrock.BedrockServer; import com.nukkitx.protocol.bedrock.v390.Bedrock_v390; import lombok.Getter; +import lombok.Setter; import org.geysermc.common.AuthType; import org.geysermc.common.PlatformType; import org.geysermc.connector.bootstrap.GeyserBootstrap; @@ -79,6 +80,7 @@ public class GeyserConnector { private static GeyserConnector instance; private RemoteServer remoteServer; + @Setter private AuthType authType; private boolean shuttingDown = false; diff --git a/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java b/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java index 5cc6d06f3e2..8683f80cd3b 100644 --- a/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java +++ b/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java @@ -33,6 +33,8 @@ import org.geysermc.connector.network.translators.world.CachedChunkManager; import org.geysermc.connector.network.translators.world.WorldManager; +import java.nio.file.Path; + public interface GeyserBootstrap { CachedChunkManager DEFAULT_CHUNK_MANAGER = new CachedChunkManager(); @@ -83,4 +85,11 @@ public interface GeyserBootstrap { default WorldManager getWorldManager() { return DEFAULT_CHUNK_MANAGER; } + + /** + * Return the data folder where files get stored + * + * @return Path location of data folder + */ + Path getConfigFolder(); } 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 88b9e795de7..8b1d0bc7860 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -75,7 +75,7 @@ public void runCommand(CommandSender sender, String command) { args = new String[0]; } else { label = command.substring(0, command.indexOf(" ")).toLowerCase(); - String argLine = command.substring(command.indexOf(" " + 1)); + String argLine = command.substring(command.indexOf(" ") + 1); args = argLine.contains(" ") ? argLine.split(" ") : new String[] { argLine }; } 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 8e59644e952..5ea942c1a98 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -62,6 +62,8 @@ public interface GeyserConfiguration { boolean isAllowThirdPartyEars(); + boolean isShowCooldown(); + String getDefaultLocale(); Path getFloodgateKeyFile(); 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 a1d0db217d0..576e5ed0d10 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -75,6 +75,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("allow-third-party-capes") private boolean allowThirdPartyCapes; + @JsonProperty("show-cooldown") + private boolean showCooldown = true; + @JsonProperty("allow-third-party-ears") private boolean allowThirdPartyEars; diff --git a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java index 8f79526de27..a59cd08fcf0 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java @@ -44,19 +44,28 @@ public class BoatEntity extends Entity { private final float ROWING_SPEED = 0.05f; public BoatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position.add(0d, entityType.getOffset(), 0d), motion, rotation.add(0, 0, 90)); + super(entityId, geyserId, entityType, position.add(0d, entityType.getOffset(), 0d), motion, rotation.add(90, 0, 90)); } @Override public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { - // Rotation is basically only called when entering/exiting a boat. // We don't include the rotation (y) as it causes the boat to appear sideways - super.moveAbsolute(session, position.add(0d, this.entityType.getOffset(), 0d), Vector3f.from(0, 0, rotation.getZ() + 90), isOnGround, teleported); + super.moveAbsolute(session, position.add(0d, this.entityType.getOffset(), 0d), Vector3f.from(rotation.getX() + 90, 0, rotation.getX() + 90), isOnGround, teleported); } @Override public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { - super.moveRelative(session, relX, relY, relZ, Vector3f.from(0, 0, rotation.getZ()), isOnGround); + super.moveRelative(session, relX, relY, relZ, Vector3f.from(rotation.getX(), 0, rotation.getX()), isOnGround); + } + + @Override + public void updatePositionAndRotation(GeyserSession session, double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) { + moveRelative(session, moveX, moveY, moveZ, yaw + 90, pitch, isOnGround); + } + + @Override + public void updateRotation(GeyserSession session, float yaw, float pitch, boolean isOnGround) { + moveRelative(session, 0, 0, 0, Vector3f.from(yaw + 90, 0, 0), isOnGround); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index c5fcde9ef84..d5ae391c00f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -78,6 +78,11 @@ public class Entity { */ protected Vector3f rotation; + /** + * Saves if the entity should be on the ground. Otherwise entities like parrots are flapping when rotating + */ + protected boolean onGround; + protected float scale = 1; protected EntityType entityType; @@ -150,11 +155,12 @@ public boolean despawnEntity(GeyserSession session) { } public void moveRelative(GeyserSession session, double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) { - moveRelative(session, relX, relY, relZ, Vector3f.from(yaw, pitch, yaw), isOnGround); + moveRelative(session, relX, relY, relZ, Vector3f.from(yaw, pitch, this.rotation.getZ()), isOnGround); } public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { setRotation(rotation); + setOnGround(isOnGround); this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ); MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); @@ -168,12 +174,13 @@ public void moveRelative(GeyserSession session, double relX, double relY, double } public void moveAbsolute(GeyserSession session, Vector3f position, float yaw, float pitch, boolean isOnGround, boolean teleported) { - moveAbsolute(session, position, Vector3f.from(yaw, pitch, yaw), isOnGround, teleported); + moveAbsolute(session, position, Vector3f.from(yaw, pitch, this.rotation.getZ()), isOnGround, teleported); } public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { setPosition(position); setRotation(rotation); + setOnGround(isOnGround); MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); moveEntityPacket.setRuntimeEntityId(geyserId); @@ -185,6 +192,52 @@ public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rota session.sendUpstreamPacket(moveEntityPacket); } + /** + * Teleports an entity to a new location. Used in JavaEntityTeleportTranslator. + * @param session GeyserSession. + * @param position The new position of the entity. + * @param yaw The new yaw of the entity. + * @param pitch The new pitch of the entity. + * @param isOnGround Whether the entity is currently on the ground. + */ + public void teleport(GeyserSession session, Vector3f position, float yaw, float pitch, boolean isOnGround) { + moveAbsolute(session, position, yaw, pitch, isOnGround, false); + } + + /** + * Updates an entity's head position. Used in JavaEntityHeadLookTranslator. + * @param session GeyserSession. + * @param headYaw The new head rotation of the entity. + */ + public void updateHeadLookRotation(GeyserSession session, float headYaw) { + moveRelative(session, 0, 0, 0, Vector3f.from(headYaw, rotation.getY(), rotation.getZ()), onGround); + } + + /** + * Updates an entity's position and rotation. Used in JavaEntityPositionRotationTranslator. + * @param session GeyserSession + * @param moveX The new X offset of the current position. + * @param moveY The new Y offset of the current position. + * @param moveZ The new Z offset of the current position. + * @param yaw The new yaw of the entity. + * @param pitch The new pitch of the entity. + * @param isOnGround Whether the entity is currently on the ground. + */ + public void updatePositionAndRotation(GeyserSession session, double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) { + moveRelative(session, moveX, moveY, moveZ, Vector3f.from(rotation.getX(), pitch, yaw), isOnGround); + } + + /** + * Updates an entity's rotation. Used in JavaEntityRotationTranslator. + * @param session GeyserSession. + * @param yaw The new yaw of the entity. + * @param pitch The new pitch of the entity. + * @param isOnGround Whether the entity is currently on the ground. + */ + public void updateRotation(GeyserSession session, float yaw, float pitch, boolean isOnGround) { + updatePositionAndRotation(session, 0, 0, 0, yaw, pitch, isOnGround); + } + public void updateBedrockAttributes(GeyserSession session) { if (!valid) return; diff --git a/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java index 1711fd38c31..00cfc8b5445 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java @@ -62,6 +62,11 @@ public void spawnEntity(GeyserSession session) { session.getConnector().getLogger().debug("Spawned painting on " + position); } + @Override + public void updateHeadLookRotation(GeyserSession session, float headYaw) { + // Do nothing, as head look messes up paintings + } + public Vector3f fixOffset(boolean toBedrock) { if (toBedrock) { Vector3f position = super.position; diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index aa7848da233..594f139a08a 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -57,7 +57,6 @@ public class PlayerEntity extends LivingEntity { private String username; private long lastSkinUpdate = -1; private boolean playerList = true; - private boolean onGround; private final EntityEffectCache effectCache; private Entity leftParrot; @@ -144,7 +143,7 @@ public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rota setPosition(position); setRotation(rotation); - this.onGround = isOnGround; + setOnGround(isOnGround); MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); movePlayerPacket.setRuntimeEntityId(geyserId); @@ -171,7 +170,7 @@ public void moveRelative(GeyserSession session, double relX, double relY, double setRotation(rotation); this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ); - this.onGround = isOnGround; + setOnGround(isOnGround); MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); movePlayerPacket.setRuntimeEntityId(geyserId); @@ -188,6 +187,35 @@ public void moveRelative(GeyserSession session, double relX, double relY, double } } + @Override + public void updateHeadLookRotation(GeyserSession session, float headYaw) { + moveRelative(session, 0, 0, 0, Vector3f.from(rotation.getX(), rotation.getY(), headYaw), onGround); + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); + movePlayerPacket.setRuntimeEntityId(geyserId); + movePlayerPacket.setPosition(position); + movePlayerPacket.setRotation(getBedrockRotation()); + movePlayerPacket.setMode(MovePlayerPacket.Mode.ROTATION); + session.sendUpstreamPacket(movePlayerPacket); + } + + @Override + public void updatePositionAndRotation(GeyserSession session, double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) { + moveRelative(session, moveX, moveY, moveZ, yaw, pitch, isOnGround); + } + + @Override + public void updateRotation(GeyserSession session, float yaw, float pitch, boolean isOnGround) { + super.updateRotation(session, yaw, pitch, isOnGround); + // Both packets need to be sent or else player head rotation isn't correctly updated + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); + movePlayerPacket.setRuntimeEntityId(geyserId); + movePlayerPacket.setPosition(position); + movePlayerPacket.setRotation(getBedrockRotation()); + movePlayerPacket.setOnGround(isOnGround); + movePlayerPacket.setMode(MovePlayerPacket.Mode.ROTATION); + session.sendUpstreamPacket(movePlayerPacket); + } + @Override public void setPosition(Vector3f position) { this.position = position.add(0, entityType.getOffset(), 0); @@ -227,7 +255,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } // Parrot occupying shoulder - if (entityMetadata.getId() == 18 || entityMetadata.getId() == 19) { + if ((entityMetadata.getId() == 18 && leftParrot == null) || (entityMetadata.getId() == 19 && rightParrot == null)) { // null check since this code just creates the parrot CompoundTag tag = (CompoundTag) entityMetadata.getValue(); if (tag != null && !tag.isEmpty()) { // The parrot is a separate entity in Bedrock, but part of the player entity in Java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java index b28ad99f571..fd9fd999f1a 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java @@ -27,21 +27,58 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.Attribute; import com.nukkitx.protocol.bedrock.data.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.AttributeUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; public class PigEntity extends AnimalEntity { + // For updating the pig heart visual easier + private float health = 20f; + public PigEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 8) { + health = (float) entityMetadata.getValue(); + updateBedrockAttributes(session); + } + if (entityMetadata.getId() == 16) { metadata.getFlags().setFlag(EntityFlag.SADDLED, (boolean) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); } + + @Override + public void updateBedrockAttributes(GeyserSession session) { + if (!valid) return; + + float maxHealth = attributes.containsKey(AttributeType.MAX_HEALTH) ? attributes.get(AttributeType.MAX_HEALTH).getValue() : 20f; + + List attributesLocal = new ArrayList<>(); + for (Map.Entry entry : this.attributes.entrySet()) { + if (!entry.getValue().getType().isBedrockAttribute()) + continue; + + attributesLocal.add(AttributeUtils.getBedrockAttribute(entry.getValue())); + } + attributesLocal.add(new Attribute("minecraft:health", 0.0f, maxHealth, health, maxHealth)); + + UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); + updateAttributesPacket.setRuntimeEntityId(geyserId); + updateAttributesPacket.setAttributes(attributesLocal); + session.sendUpstreamPacket(updateAttributesPacket); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java index 63a67a0a71b..de9bcb4ec12 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java @@ -38,6 +38,11 @@ public CatEntity(long entityId, long geyserId, EntityType entityType, Vector3f p super(entityId, geyserId, entityType, position, motion, rotation); } + @Override + public void updateRotation(GeyserSession session, float yaw, float pitch, boolean isOnGround) { + moveRelative(session, 0, 0, 0, Vector3f.from(this.rotation.getX(), pitch, yaw), isOnGround); + } + @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 18) { diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/AbstractMerchantEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/AbstractMerchantEntity.java index ddeb31bd13d..11028b79e36 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/AbstractMerchantEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/AbstractMerchantEntity.java @@ -28,10 +28,16 @@ import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.entity.living.AgeableEntity; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; public class AbstractMerchantEntity extends AgeableEntity { public AbstractMerchantEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } + + @Override + public void teleport(GeyserSession session, Vector3f position, float yaw, float pitch, boolean isOnGround) { + super.teleport(session, position, yaw - 180, pitch, isOnGround); + } } 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 5491d6e5dbe..d2b87af149c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -47,6 +47,8 @@ import com.nukkitx.protocol.bedrock.BedrockServerSession; import com.nukkitx.protocol.bedrock.data.*; import com.nukkitx.protocol.bedrock.packet.*; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2LongMap; import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import lombok.Getter; @@ -57,7 +59,6 @@ import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.remote.RemoteServer; import org.geysermc.connector.network.session.auth.AuthData; @@ -108,6 +109,9 @@ public class GeyserSession implements CommandSender { @Setter private TeleportCache teleportCache; + @Getter + private final Long2ObjectMap storedMaps = new Long2ObjectOpenHashMap<>(); + /** * A map of Vector3i positions to Java entity IDs. * Used for translating Bedrock block actions to Java entity actions. @@ -175,6 +179,18 @@ public class GeyserSession implements CommandSender { @Setter private long lastInteractedVillagerEid; + /** + * The current attack speed of the player. Used for sending proper cooldown timings. + */ + @Setter + private double attackSpeed; + /** + * The time of the last hit. Used to gauge how long the cooldown is taking. + * This is a session variable in order to prevent more scheduled threads than necessary. + */ + @Setter + private long lastHitTime; + private MinecraftProtocol protocol; public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java index bfd4a90a5e0..84fb309e454 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java @@ -26,6 +26,7 @@ package org.geysermc.connector.network.translators.bedrock; import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.data.EntityDataMap; import com.nukkitx.protocol.bedrock.data.EntityFlag; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.network.session.GeyserSession; @@ -74,33 +75,43 @@ public void translate(InteractPacket packet, GeyserSession session) { Entity interactEntity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId()); if (interactEntity == null) return; + EntityDataMap entityMetadata = interactEntity.getMetadata(); String interactiveTag; switch (interactEntity.getEntityType()) { - case PIG: - if (interactEntity.getMetadata().getFlags().getFlag(EntityFlag.SADDLED)) { - interactiveTag = "action.interact.mount"; - } else interactiveTag = ""; + case BOAT: + interactiveTag = "action.interact.ride.boat"; break; - case HORSE: - case SKELETON_HORSE: - case ZOMBIE_HORSE: case DONKEY: - case MULE: + case HORSE: case LLAMA: + case MULE: + case SKELETON_HORSE: case TRADER_LLAMA: - if (interactEntity.getMetadata().getFlags().getFlag(EntityFlag.TAMED)) { + case ZOMBIE_HORSE: + if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { interactiveTag = "action.interact.ride.horse"; } else { interactiveTag = "action.interact.mount"; } break; - case BOAT: - interactiveTag = "action.interact.ride.boat"; - break; case MINECART: interactiveTag = "action.interact.ride.minecart"; break; + case PIG: + if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { + interactiveTag = "action.interact.mount"; + } else interactiveTag = ""; + break; + case VILLAGER: + if (entityMetadata.getInt(EntityData.VARIANT) != 14 && entityMetadata.getInt(EntityData.VARIANT) != 0 + && entityMetadata.getFloat(EntityData.SCALE) >= 0.75f) { // Not a nitwit, has a profession and is not a baby + interactiveTag = "action.interact.trade"; + } else interactiveTag = ""; + break; + case WANDERING_TRADER: + interactiveTag = "action.interact.trade"; // Since you can always trade with a wandering villager, presumably. + break; default: return; // No need to process any further since there is no interactive tag } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLevelSoundEventTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLevelSoundEventTranslator.java index 6395f0a132a..08ad10bfbc8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLevelSoundEventTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLevelSoundEventTranslator.java @@ -25,10 +25,12 @@ package org.geysermc.connector.network.translators.bedrock; +import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.utils.CooldownUtils; @Translator(packet = LevelSoundEventPacket.class) public class BedrockLevelSoundEventTranslator extends PacketTranslator { @@ -37,5 +39,12 @@ public class BedrockLevelSoundEventTranslator extends PacketTranslator { + + @Override + public void translate(MapInfoRequestPacket packet, GeyserSession session) { + long mapID = packet.getUniqueMapId(); + + if (session.getStoredMaps().containsKey(mapID)) { + // Delay the packet 100ms to prevent the client from ignoring the packet + GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> { + session.sendUpstreamPacket(session.getStoredMaps().get(mapID)); + session.getStoredMaps().remove(mapID); + }, 100, TimeUnit.MILLISECONDS); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java index 5fc7f41aed3..1dfa27743f0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java @@ -32,6 +32,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerChangeHeldItemPacket; import com.nukkitx.protocol.bedrock.data.ContainerId; import com.nukkitx.protocol.bedrock.packet.MobEquipmentPacket; +import org.geysermc.connector.utils.CooldownUtils; @Translator(packet = MobEquipmentPacket.class) public class BedrockMobEquipmentTranslator extends PacketTranslator { @@ -47,5 +48,8 @@ public void translate(MobEquipmentPacket packet, GeyserSession session) { ClientPlayerChangeHeldItemPacket changeHeldItemPacket = new ClientPlayerChangeHeldItemPacket(packet.getHotbarSlot()); session.sendDownstreamPacket(changeHeldItemPacket); + + // Java sends a cooldown indicator whenever you switch an item + CooldownUtils.sendCooldown(session); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index cb8613c8c44..1962f62ecbd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java @@ -28,8 +28,8 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.message.Message; -import com.nukkitx.nbt.CompoundTagBuilder; import com.github.steveice10.opennbt.tag.builtin.*; +import com.nukkitx.nbt.CompoundTagBuilder; import com.nukkitx.nbt.tag.CompoundTag; import com.nukkitx.nbt.tag.Tag; import com.nukkitx.protocol.bedrock.data.ItemData; @@ -41,11 +41,7 @@ import org.geysermc.connector.utils.MessageUtils; import org.reflections.Reflections; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; public abstract class ItemTranslator { @@ -126,12 +122,20 @@ public static ItemData translateToBedrock(GeyserSession session, ItemStack stack ItemEntry bedrockItem = ItemRegistry.getItem(stack); - ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), stack.getNbt() != null ? stack.getNbt().clone() : null); + com.github.steveice10.opennbt.tag.builtin.CompoundTag nbt = stack.getNbt() != null ? stack.getNbt().clone() : null; + + // This is a fallback for maps with no nbt + if (nbt == null && bedrockItem.getJavaIdentifier().equals("minecraft:filled_map")) { + nbt = new com.github.steveice10.opennbt.tag.builtin.CompoundTag(""); + nbt.put(new IntTag("map", 0)); + } + + ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), nbt); - if (itemStack.getNbt() != null) { + if (nbt != null) { for (NbtItemStackTranslator translator : NBT_TRANSLATORS) { if (translator.acceptItem(bedrockItem)) { - translator.translateToBedrock(itemStack.getNbt(), bedrockItem); + translator.translateToBedrock(nbt, bedrockItem); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java index 8c418c0f2ef..51029b83dcb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java @@ -57,4 +57,9 @@ public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { itemTag.remove("map_uuid"); } } + + @Override + public boolean acceptItem(ItemEntry itemEntry) { + return itemEntry.getJavaIdentifier().equals("minecraft:filled_map"); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityHeadLookTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityHeadLookTranslator.java index e26a5880fe8..115cae55e4b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityHeadLookTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityHeadLookTranslator.java @@ -25,17 +25,12 @@ package org.geysermc.connector.network.translators.java.entity; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityHeadLookPacket; import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityHeadLookPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; -import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; - @Translator(packet = ServerEntityHeadLookPacket.class) public class JavaEntityHeadLookTranslator extends PacketTranslator { @@ -48,21 +43,6 @@ public void translate(ServerEntityHeadLookPacket packet, GeyserSession session) if (entity == null) return; - entity.setRotation(Vector3f.from(entity.getRotation().getX(), entity.getRotation().getY(), packet.getHeadYaw())); - - if (entity.getEntityType() != EntityType.PLAYER && entity.getEntityType() != EntityType.PAINTING) { - MoveEntityAbsolutePacket moveEntityAbsolutePacket = new MoveEntityAbsolutePacket(); - moveEntityAbsolutePacket.setRuntimeEntityId(entity.getGeyserId()); - moveEntityAbsolutePacket.setPosition(entity.getPosition()); - moveEntityAbsolutePacket.setRotation(entity.getBedrockRotation()); - session.sendUpstreamPacket(moveEntityAbsolutePacket); - } else { - MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); - movePlayerPacket.setRuntimeEntityId(entity.getGeyserId()); - movePlayerPacket.setPosition(entity.getPosition()); - movePlayerPacket.setRotation(entity.getBedrockRotation()); - movePlayerPacket.setMode(MovePlayerPacket.Mode.ROTATION); - session.sendUpstreamPacket(movePlayerPacket); - } + entity.updateHeadLookRotation(session, packet.getHeadYaw()); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPositionRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPositionRotationTranslator.java index 477c8f261ea..c4bb799e652 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPositionRotationTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPositionRotationTranslator.java @@ -25,14 +25,12 @@ package org.geysermc.connector.network.translators.java.entity; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityPositionRotationPacket; import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityPositionRotationPacket; - @Translator(packet = ServerEntityPositionRotationPacket.class) public class JavaEntityPositionRotationTranslator extends PacketTranslator { @@ -43,10 +41,7 @@ public void translate(ServerEntityPositionRotationPacket packet, GeyserSession s entity = session.getPlayerEntity(); } if (entity == null) return; - if (entity.getEntityType() == EntityType.BOAT) { - entity.moveRelative(session, packet.getMoveX(), packet.getMoveY(), packet.getMoveZ(), packet.getYaw() - 90, packet.getPitch(), packet.isOnGround()); - } else { - entity.moveRelative(session, packet.getMoveX(), packet.getMoveY(), packet.getMoveZ(), packet.getYaw(), packet.getPitch(), packet.isOnGround()); - } + + entity.updatePositionAndRotation(session, packet.getMoveX(), packet.getMoveY(), packet.getMoveZ(), packet.getYaw(), packet.getPitch(), packet.isOnGround()); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java index d08bb1afbaa..744b11e9644 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java @@ -25,6 +25,8 @@ package org.geysermc.connector.network.translators.java.entity; +import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityPropertiesPacket; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.network.session.GeyserSession; @@ -32,17 +34,16 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.AttributeUtils; -import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityPropertiesPacket; - @Translator(packet = ServerEntityPropertiesPacket.class) public class JavaEntityPropertiesTranslator extends PacketTranslator { @Override public void translate(ServerEntityPropertiesPacket packet, GeyserSession session) { + boolean isSessionPlayer = false; Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { entity = session.getPlayerEntity(); + isSessionPlayer = true; } if (entity == null) return; @@ -54,6 +55,13 @@ public void translate(ServerEntityPropertiesPacket packet, GeyserSession session case GENERIC_ATTACK_DAMAGE: entity.getAttributes().put(AttributeType.ATTACK_DAMAGE, AttributeType.ATTACK_DAMAGE.getAttribute((float) AttributeUtils.calculateValue(attribute))); break; + case GENERIC_ATTACK_SPEED: + if (isSessionPlayer) { + // Get attack speed value for use in sending the faux cooldown + double attackSpeed = AttributeUtils.calculateValue(attribute); + session.setAttackSpeed(attackSpeed); + } + break; case GENERIC_FLYING_SPEED: entity.getAttributes().put(AttributeType.FLYING_SPEED, AttributeType.FLYING_SPEED.getAttribute((float) AttributeUtils.calculateValue(attribute))); entity.getAttributes().put(AttributeType.MOVEMENT_SPEED, AttributeType.MOVEMENT_SPEED.getAttribute((float) AttributeUtils.calculateValue(attribute))); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityRotationTranslator.java index fbeb1969253..c1d3e57879b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityRotationTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityRotationTranslator.java @@ -25,17 +25,12 @@ package org.geysermc.connector.network.translators.java.entity; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityRotationPacket; import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityRotationPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; -import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; - @Translator(packet = ServerEntityRotationPacket.class) public class JavaEntityRotationTranslator extends PacketTranslator { @@ -47,23 +42,6 @@ public void translate(ServerEntityRotationPacket packet, GeyserSession session) } if (entity == null) return; - // entity.moveRelative(packet.getMovementX(), packet.getMovementY(), packet.getMovementZ(), packet.getYaw(), packet.getPitch()); - entity.setRotation(Vector3f.from(packet.getYaw(), packet.getPitch(), packet.getYaw())); - - if (entity.getEntityType() != EntityType.PLAYER) { - MoveEntityAbsolutePacket moveEntityAbsolutePacket = new MoveEntityAbsolutePacket(); - moveEntityAbsolutePacket.setRuntimeEntityId(entity.getGeyserId()); - moveEntityAbsolutePacket.setPosition(entity.getPosition()); - moveEntityAbsolutePacket.setRotation(entity.getBedrockRotation()); - session.sendUpstreamPacket(moveEntityAbsolutePacket); - } else { - MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); - movePlayerPacket.setRuntimeEntityId(entity.getGeyserId()); - movePlayerPacket.setPosition(entity.getPosition()); - movePlayerPacket.setRotation(entity.getBedrockRotation()); - movePlayerPacket.setOnGround(packet.isOnGround()); - movePlayerPacket.setMode(MovePlayerPacket.Mode.ROTATION); - session.sendUpstreamPacket(movePlayerPacket); - } + entity.updateRotation(session, packet.getYaw(), packet.getPitch(), packet.isOnGround()); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityTeleportTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityTeleportTranslator.java index 9a4686728ce..cf01d214dde 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityTeleportTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityTeleportTranslator.java @@ -44,6 +44,6 @@ public void translate(ServerEntityTeleportPacket packet, GeyserSession session) } if (entity == null) return; - entity.moveAbsolute(session, Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), packet.isOnGround(), false); + entity.teleport(session, Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), packet.isOnGround()); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java index 920969a755a..742fa97e937 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java @@ -31,10 +31,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnEntityPacket; import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.FallingBlockEntity; -import org.geysermc.connector.entity.FishingHookEntity; -import org.geysermc.connector.entity.ItemFrameEntity; +import org.geysermc.connector.entity.*; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -73,6 +70,10 @@ public void translate(ServerSpawnEntityPacket packet, GeyserSession session) { // Fishing bobbers need the owner for the line entity = new FishingHookEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), type, position, motion, rotation, (ProjectileData) packet.getData()); + } else if (packet.getType() == EntityType.BOAT) { + // Initial rotation is incorrect + entity = new BoatEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), + type, position, motion, Vector3f.from(packet.getYaw(), 0, packet.getYaw())); } else { Constructor entityConstructor = entityClass.getConstructor(long.class, long.class, org.geysermc.connector.entity.type.EntityType.class, Vector3f.class, Vector3f.class, Vector3f.class); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java index c8be3a56e77..5a3ebabaf87 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java @@ -42,6 +42,7 @@ public class JavaMapDataTranslator extends PacketTranslator @Override public void translate(ServerMapDataPacket packet, GeyserSession session) { ClientboundMapItemDataPacket mapItemDataPacket = new ClientboundMapItemDataPacket(); + boolean shouldStore = false; mapItemDataPacket.setUniqueMapId(packet.getMapId()); mapItemDataPacket.setDimensionId(session.getPlayerEntity().getDimension()); @@ -55,6 +56,11 @@ public void translate(ServerMapDataPacket packet, GeyserSession session) { mapItemDataPacket.setWidth(data.getColumns()); mapItemDataPacket.setHeight(data.getRows()); + // We have a full map image, this usually only happens on spawn for the initial image + if (mapItemDataPacket.getWidth() == 128 && mapItemDataPacket.getHeight() == 128) { + shouldStore = true; + } + // Every int entry is an ABGR color int[] colors = new int[data.getData().length]; @@ -76,6 +82,12 @@ public void translate(ServerMapDataPacket packet, GeyserSession session) { id++; } - session.getUpstream().getSession().sendPacket(mapItemDataPacket); + // Store the map to send when the client requests it, as bedrock expects the data after a MapInfoRequestPacket + if (shouldStore) { + session.getStoredMaps().put(mapItemDataPacket.getUniqueMapId(), mapItemDataPacket); + } + + // Send anyway just in case + session.sendUpstreamPacket(mapItemDataPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/AttributeUtils.java b/connector/src/main/java/org/geysermc/connector/utils/AttributeUtils.java index a69a5f19752..943fd2394bb 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/AttributeUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/AttributeUtils.java @@ -70,7 +70,12 @@ public static com.nukkitx.protocol.bedrock.data.Attribute getBedrockAttribute(At return new com.nukkitx.protocol.bedrock.data.Attribute(type.getBedrockIdentifier(), attribute.getMinimum(), attribute.getMaximum(), attribute.getValue(), attribute.getDefaultValue()); } - //https://minecraft.gamepedia.com/Attribute#Modifiers + /** + * Retrieve the base attribute value with all modifiers applied. + * https://minecraft.gamepedia.com/Attribute#Modifiers + * @param attribute The attribute to calculate the total value. + * @return The finished attribute with all modifiers applied. + */ public static double calculateValue(com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute attribute) { double base = attribute.getValue(); for (AttributeModifier modifier : attribute.getModifiers()) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java b/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java new file mode 100644 index 00000000000..9444fc2a453 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.utils; + +import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; + +import java.util.concurrent.TimeUnit; + +/** + * Manages the sending of a cooldown indicator to the Bedrock player as there is no cooldown indicator in Bedrock. + * Much of the work here is from the wonderful folks from ViaRewind: https://github.com/ViaVersion/ViaRewind + */ +public class CooldownUtils { + + private final static boolean SHOW_COOLDOWN; + + static { + SHOW_COOLDOWN = GeyserConnector.getInstance().getConfig().isShowCooldown(); + } + + /** + * Starts sending the fake cooldown to the Bedrock client. + * @param session GeyserSession + */ + public static void sendCooldown(GeyserSession session) { + if (!SHOW_COOLDOWN) return; + if (session.getAttackSpeed() == 0.0 || session.getAttackSpeed() > 20) return; // 0.0 usually happens on login and causes issues with visuals; anything above 20 means a plugin like OldCombatMechanics is being used + // Needs to be sent or no subtitle packet is recognized by the client + SetTitlePacket titlePacket = new SetTitlePacket(); + titlePacket.setType(SetTitlePacket.Type.SET_TITLE); + titlePacket.setText(" "); + session.sendUpstreamPacket(titlePacket); + session.setLastHitTime(System.currentTimeMillis()); + long lastHitTime = session.getLastHitTime(); // Used later to prevent multiple scheduled cooldown threads + computeCooldown(session, lastHitTime); + } + + /** + * Keeps updating the cooldown until the bar is complete. + * @param session GeyserSession + * @param lastHitTime The time of the last hit. Used to gauge how long the cooldown is taking. + */ + private static void computeCooldown(GeyserSession session, long lastHitTime) { + if (session.isClosed()) return; // Don't run scheduled tasks if the client left + if (lastHitTime != session.getLastHitTime()) return; // Means another cooldown has started so there's no need to continue this one + SetTitlePacket titlePacket = new SetTitlePacket(); + titlePacket.setType(SetTitlePacket.Type.SET_SUBTITLE); + titlePacket.setText(getTitle(session)); + titlePacket.setFadeInTime(0); + titlePacket.setFadeOutTime(5); + titlePacket.setStayTime(2); + session.sendUpstreamPacket(titlePacket); + if (hasCooldown(session)) { + session.getConnector().getGeneralThreadPool().schedule(() -> computeCooldown(session, lastHitTime), 50, TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50 + } else { + SetTitlePacket removeTitlePacket = new SetTitlePacket(); + removeTitlePacket.setType(SetTitlePacket.Type.SET_SUBTITLE); + removeTitlePacket.setText(" "); + session.sendUpstreamPacket(removeTitlePacket); + } + } + + private static boolean hasCooldown(GeyserSession session) { + long time = System.currentTimeMillis() - session.getLastHitTime(); + double cooldown = restrain(((double) time) * session.getAttackSpeed() / 1000d, 1.5); + return cooldown < 1.1; + } + + + private static double restrain(double x, double max) { + if (x < 0d) + return 0d; + return Math.min(x, max); + } + + private static String getTitle(GeyserSession session) { + long time = System.currentTimeMillis() - session.getLastHitTime(); + double cooldown = restrain(((double) time) * session.getAttackSpeed() / 1000d, 1); + + int darkGrey = (int) Math.floor(10d * cooldown); + int grey = 10 - darkGrey; + StringBuilder builder = new StringBuilder("§8"); + while (darkGrey > 0) { + builder.append("˙"); + darkGrey--; + } + builder.append("§7"); + while (grey > 0) { + builder.append("˙"); + grey--; + } + return builder.toString(); + } + +} 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 f55cb261e38..e213fe6cc95 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -33,6 +33,7 @@ import java.io.*; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.zip.ZipFile; @@ -49,7 +50,8 @@ public class LocaleUtils { static { // Create the locales folder - File localesFolder = new File("locales/"); + File localesFolder = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales").toFile(); + //noinspection ResultOfMethodCallIgnored localesFolder.mkdir(); // Download the latest asset list and cache it @@ -132,7 +134,7 @@ public static void downloadAndLoadLocale(String locale) { * @param locale Locale to download */ private static void downloadLocale(String locale) { - File localeFile = new File("locales/" + locale + ".json"); + File localeFile = Paths.get(GeyserConnector.getInstance().getBootstrap().getConfigFolder().toString(),"locales",locale + ".json").toFile(); // Check if we have already downloaded the locale file if (localeFile.exists()) { @@ -149,7 +151,7 @@ private static void downloadLocale(String locale) { // Get the hash and download the locale String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); - WebUtils.downloadFile("http://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, "locales/" + locale + ".json"); + WebUtils.downloadFile("http://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString()); } /** @@ -205,10 +207,11 @@ private static void downloadEN_US(File localeFile) { GeyserConnector.getInstance().getLogger().debug("Download URL: " + smallestURL); // Download the smallest JAR (client or server) - WebUtils.downloadFile(smallestURL, "tmp_locale.jar"); + Path tmpFilePath = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("tmp_locale.jar"); + WebUtils.downloadFile(smallestURL, tmpFilePath.toString()); // Load in the JAR as a zip and extract the file - ZipFile localeJar = new ZipFile("tmp_locale.jar"); + ZipFile localeJar = new ZipFile(tmpFilePath.toString()); InputStream inputStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json")); FileOutputStream outputStream = new FileOutputStream(localeFile); @@ -227,7 +230,7 @@ private static void downloadEN_US(File localeFile) { localeJar.close(); // Delete the nolonger needed client/server jar - Files.delete(Paths.get("tmp_locale.jar")); + Files.delete(tmpFilePath); } catch (Exception e) { throw new AssertionError("Unable to download and extract en_us locale!", e); } diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 931e0a8d4cc..e0e0771da14 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -8,7 +8,8 @@ # -------------------------------- bedrock: - # The IP address that will listen for connections + # The IP address that will listen for connections. + # There is no reason to change this unless you want to limit what IPs can connect to your server. address: 0.0.0.0 # The port that will listen for connections port: 19132 @@ -74,6 +75,9 @@ allow-third-party-capes: true # MinecraftCapes allow-third-party-ears: false +# Allow a fake cooldown indicator to be sent. Bedrock players do not see a cooldown as they still use 1.8 combat +show-cooldown: true + # The default locale if we dont have the one the client requested default-locale: en_us