From 9369b2020907111b9ab10da430e3e254055052cf Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 16 Jun 2020 20:03:28 -0400 Subject: [PATCH] Add 1.9+ PvP 'Cooldown' (#768) * Add 1.9+ PvP 'Cooldown' This commit adds a subtitle that acts as the Java cooldown. This is an optional feature disabled in the config with `show-cooldown`. This does not appear on plugins that use OldCombatMechanics. * No need to bump up the config version; I was just tested with OldCombatMechanics * Use simpler casting * Use session variable of lastHitTime for theoretically better performance * Reuse attribute value calculation from AttributeUtils * Remove unused imports * Revert config version update in config.yml --- .../sponge/GeyserSpongeConfiguration.java | 5 + .../configuration/GeyserConfiguration.java | 2 + .../GeyserJacksonConfiguration.java | 3 + .../network/session/GeyserSession.java | 12 ++ .../BedrockLevelSoundEventTranslator.java | 9 ++ .../BedrockMobEquipmentTranslator.java | 4 + .../JavaEntityPropertiesTranslator.java | 14 +- .../connector/utils/AttributeUtils.java | 7 +- .../connector/utils/CooldownUtils.java | 121 ++++++++++++++++++ connector/src/main/resources/config.yml | 3 + 10 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java 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 2b02ec0d..79e7621c 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 class GeyserSpongeConfiguration implements GeyserConfiguration { 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/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java index 8e59644e..5ea942c1 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 a1d0db21..576e5ed0 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/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index d9615e16..d2b87af1 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 @@ -179,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/BedrockLevelSoundEventTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLevelSoundEventTranslator.java index 6395f0a1..08ad10bf 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 { @@ -47,5 +48,8 @@ public class BedrockMobEquipmentTranslator 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 class JavaEntityPropertiesTranslator extends PacketTranslator 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/resources/config.yml b/connector/src/main/resources/config.yml index 931e0a8d..a68f32a1 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -74,6 +74,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