forked from GeyserMC/Geyser
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:
parent
256c62ce88
commit
9369b20209
10 changed files with 176 additions and 4 deletions
|
@ -129,6 +129,11 @@ public class GeyserSpongeConfiguration implements GeyserConfiguration {
|
||||||
return node.getNode("allow-third-party-ears").getBoolean(false);
|
return node.getNode("allow-third-party-ears").getBoolean(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShowCooldown() {
|
||||||
|
return node.getNode("show-cooldown").getBoolean(true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDefaultLocale() {
|
public String getDefaultLocale() {
|
||||||
return node.getNode("default-locale").getString("en_us");
|
return node.getNode("default-locale").getString("en_us");
|
||||||
|
|
|
@ -62,6 +62,8 @@ public interface GeyserConfiguration {
|
||||||
|
|
||||||
boolean isAllowThirdPartyEars();
|
boolean isAllowThirdPartyEars();
|
||||||
|
|
||||||
|
boolean isShowCooldown();
|
||||||
|
|
||||||
String getDefaultLocale();
|
String getDefaultLocale();
|
||||||
|
|
||||||
Path getFloodgateKeyFile();
|
Path getFloodgateKeyFile();
|
||||||
|
|
|
@ -75,6 +75,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
||||||
@JsonProperty("allow-third-party-capes")
|
@JsonProperty("allow-third-party-capes")
|
||||||
private boolean allowThirdPartyCapes;
|
private boolean allowThirdPartyCapes;
|
||||||
|
|
||||||
|
@JsonProperty("show-cooldown")
|
||||||
|
private boolean showCooldown = true;
|
||||||
|
|
||||||
@JsonProperty("allow-third-party-ears")
|
@JsonProperty("allow-third-party-ears")
|
||||||
private boolean allowThirdPartyEars;
|
private boolean allowThirdPartyEars;
|
||||||
|
|
||||||
|
|
|
@ -179,6 +179,18 @@ public class GeyserSession implements CommandSender {
|
||||||
@Setter
|
@Setter
|
||||||
private long lastInteractedVillagerEid;
|
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;
|
private MinecraftProtocol protocol;
|
||||||
|
|
||||||
public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) {
|
public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) {
|
||||||
|
|
|
@ -25,10 +25,12 @@
|
||||||
|
|
||||||
package org.geysermc.connector.network.translators.bedrock;
|
package org.geysermc.connector.network.translators.bedrock;
|
||||||
|
|
||||||
|
import com.nukkitx.protocol.bedrock.data.SoundEvent;
|
||||||
import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket;
|
import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
import org.geysermc.connector.network.translators.Translator;
|
import org.geysermc.connector.network.translators.Translator;
|
||||||
|
import org.geysermc.connector.utils.CooldownUtils;
|
||||||
|
|
||||||
@Translator(packet = LevelSoundEventPacket.class)
|
@Translator(packet = LevelSoundEventPacket.class)
|
||||||
public class BedrockLevelSoundEventTranslator extends PacketTranslator<LevelSoundEventPacket> {
|
public class BedrockLevelSoundEventTranslator extends PacketTranslator<LevelSoundEventPacket> {
|
||||||
|
@ -37,5 +39,12 @@ public class BedrockLevelSoundEventTranslator extends PacketTranslator<LevelSoun
|
||||||
public void translate(LevelSoundEventPacket packet, GeyserSession session) {
|
public void translate(LevelSoundEventPacket packet, GeyserSession session) {
|
||||||
// lol what even :thinking:
|
// lol what even :thinking:
|
||||||
session.sendUpstreamPacket(packet);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.geysermc.connector.network.translators.Translator;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerChangeHeldItemPacket;
|
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerChangeHeldItemPacket;
|
||||||
import com.nukkitx.protocol.bedrock.data.ContainerId;
|
import com.nukkitx.protocol.bedrock.data.ContainerId;
|
||||||
import com.nukkitx.protocol.bedrock.packet.MobEquipmentPacket;
|
import com.nukkitx.protocol.bedrock.packet.MobEquipmentPacket;
|
||||||
|
import org.geysermc.connector.utils.CooldownUtils;
|
||||||
|
|
||||||
@Translator(packet = MobEquipmentPacket.class)
|
@Translator(packet = MobEquipmentPacket.class)
|
||||||
public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipmentPacket> {
|
public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipmentPacket> {
|
||||||
|
@ -47,5 +48,8 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
|
||||||
|
|
||||||
ClientPlayerChangeHeldItemPacket changeHeldItemPacket = new ClientPlayerChangeHeldItemPacket(packet.getHotbarSlot());
|
ClientPlayerChangeHeldItemPacket changeHeldItemPacket = new ClientPlayerChangeHeldItemPacket(packet.getHotbarSlot());
|
||||||
session.sendDownstreamPacket(changeHeldItemPacket);
|
session.sendDownstreamPacket(changeHeldItemPacket);
|
||||||
|
|
||||||
|
// Java sends a cooldown indicator whenever you switch an item
|
||||||
|
CooldownUtils.sendCooldown(session);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
|
|
||||||
package org.geysermc.connector.network.translators.java.entity;
|
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.Entity;
|
||||||
import org.geysermc.connector.entity.attribute.AttributeType;
|
import org.geysermc.connector.entity.attribute.AttributeType;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
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.network.translators.Translator;
|
||||||
import org.geysermc.connector.utils.AttributeUtils;
|
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)
|
@Translator(packet = ServerEntityPropertiesPacket.class)
|
||||||
public class JavaEntityPropertiesTranslator extends PacketTranslator<ServerEntityPropertiesPacket> {
|
public class JavaEntityPropertiesTranslator extends PacketTranslator<ServerEntityPropertiesPacket> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(ServerEntityPropertiesPacket packet, GeyserSession session) {
|
public void translate(ServerEntityPropertiesPacket packet, GeyserSession session) {
|
||||||
|
boolean isSessionPlayer = false;
|
||||||
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
|
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
|
||||||
if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) {
|
if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) {
|
||||||
entity = session.getPlayerEntity();
|
entity = session.getPlayerEntity();
|
||||||
|
isSessionPlayer = true;
|
||||||
}
|
}
|
||||||
if (entity == null) return;
|
if (entity == null) return;
|
||||||
|
|
||||||
|
@ -54,6 +55,13 @@ public class JavaEntityPropertiesTranslator extends PacketTranslator<ServerEntit
|
||||||
case GENERIC_ATTACK_DAMAGE:
|
case GENERIC_ATTACK_DAMAGE:
|
||||||
entity.getAttributes().put(AttributeType.ATTACK_DAMAGE, AttributeType.ATTACK_DAMAGE.getAttribute((float) AttributeUtils.calculateValue(attribute)));
|
entity.getAttributes().put(AttributeType.ATTACK_DAMAGE, AttributeType.ATTACK_DAMAGE.getAttribute((float) AttributeUtils.calculateValue(attribute)));
|
||||||
break;
|
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:
|
case GENERIC_FLYING_SPEED:
|
||||||
entity.getAttributes().put(AttributeType.FLYING_SPEED, AttributeType.FLYING_SPEED.getAttribute((float) AttributeUtils.calculateValue(attribute)));
|
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)));
|
entity.getAttributes().put(AttributeType.MOVEMENT_SPEED, AttributeType.MOVEMENT_SPEED.getAttribute((float) AttributeUtils.calculateValue(attribute)));
|
||||||
|
|
|
@ -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());
|
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) {
|
public static double calculateValue(com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute attribute) {
|
||||||
double base = attribute.getValue();
|
double base = attribute.getValue();
|
||||||
for (AttributeModifier modifier : attribute.getModifiers()) {
|
for (AttributeModifier modifier : attribute.getModifiers()) {
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -74,6 +74,9 @@ allow-third-party-capes: true
|
||||||
# MinecraftCapes
|
# MinecraftCapes
|
||||||
allow-third-party-ears: false
|
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
|
# The default locale if we dont have the one the client requested
|
||||||
default-locale: en_us
|
default-locale: en_us
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue