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
This commit is contained in:
Camotoy 2020-06-16 20:03:28 -04:00 committed by GitHub
parent 256c62ce88
commit 9369b20209
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 176 additions and 4 deletions

View file

@ -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");

View file

@ -62,6 +62,8 @@ public interface GeyserConfiguration {
boolean isAllowThirdPartyEars();
boolean isShowCooldown();
String getDefaultLocale();
Path getFloodgateKeyFile();

View file

@ -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;

View file

@ -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) {

View file

@ -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<LevelSoundEventPacket> {
@ -37,5 +39,12 @@ public class BedrockLevelSoundEventTranslator extends PacketTranslator<LevelSoun
public void translate(LevelSoundEventPacket packet, GeyserSession session) {
// lol what even :thinking:
session.sendUpstreamPacket(packet);
// Yes, what even, but thankfully we can hijack this packet to send the cooldown
if (packet.getSound() == SoundEvent.ATTACK_NODAMAGE || packet.getSound() == SoundEvent.ATTACK || packet.getSound() == SoundEvent.ATTACK_STRONG) {
// Send a faux cooldown since Bedrock has no cooldown support
// Sent here because Java still sends a cooldown if the player doesn't hit anything but Bedrock always sends a sound
CooldownUtils.sendCooldown(session);
}
}
}

View file

@ -32,6 +32,7 @@ import org.geysermc.connector.network.translators.Translator;
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<MobEquipmentPacket> {
@ -47,5 +48,8 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
ClientPlayerChangeHeldItemPacket changeHeldItemPacket = new ClientPlayerChangeHeldItemPacket(packet.getHotbarSlot());
session.sendDownstreamPacket(changeHeldItemPacket);
// Java sends a cooldown indicator whenever you switch an item
CooldownUtils.sendCooldown(session);
}
}

View file

@ -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.PacketTranslator;
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<ServerEntityPropertiesPacket> {
@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<ServerEntit
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)));

View file

@ -70,7 +70,12 @@ public class AttributeUtils {
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()) {

View file

@ -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();
}
}

View file

@ -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