Compare commits
50
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d54d2a199e | ||
|
|
b6cf670fe0 | ||
|
|
decbe90910 | ||
|
|
6891b96895 | ||
|
|
c14926037a | ||
|
|
77198d5ed8 | ||
|
|
d7a262741d | ||
|
|
a8143785d0 | ||
|
|
22e5684f49 | ||
|
|
469c2cd901 | ||
|
|
b4f6c45704 | ||
|
|
23c3e65cfb | ||
|
|
985689c52a | ||
|
|
0176376776 | ||
|
|
a67fdd2426 | ||
|
|
7c1155c02c | ||
|
|
8e5998c2c9 | ||
|
|
6543c43d7d | ||
|
|
b123eb4800 | ||
|
|
f1d74ba6ec | ||
|
|
977b9e1925 | ||
|
|
b20eabaa0e | ||
|
|
2ebadb5d47 | ||
|
|
a24466898f | ||
|
|
a43d7d1543 | ||
|
|
babc5fbd84 | ||
|
|
bebee4f1bd | ||
|
|
879c2a566a | ||
|
|
2ffa941550 | ||
|
|
6f9afb4d77 | ||
|
|
14064d378c | ||
|
|
3407b2165d | ||
|
|
23d5305d42 | ||
|
|
6eaf09eeb6 | ||
|
|
8b565a42e7 | ||
|
|
38723ce66b | ||
|
|
d898d5cfeb | ||
|
|
7acc3551ee | ||
|
|
913303a6dc | ||
|
|
16b122450f | ||
|
|
d0c796ef60 | ||
|
|
fd56ae1e36 | ||
|
|
e9b2c40fac | ||
|
|
f8a20b16e9 | ||
|
|
02ce284fb2 | ||
|
|
81fa80c736 | ||
|
|
81003411a0 | ||
|
|
109137a5cd | ||
|
|
ba062e6f02 | ||
|
|
e635d42c9a |
No files matched your search
@@ -0,0 +1,8 @@
|
||||
# Ignores all of the Intellij Project settings
|
||||
.idea/
|
||||
|
||||
# Ignore maven reduced POM built when importing/packaging project
|
||||
dependency-reduced-pom.xml
|
||||
|
||||
# Ignore maven build files
|
||||
target/
|
||||
@@ -5,6 +5,10 @@ As a result, players take negative velocity when they're on the ground clientsid
|
||||
This plugin handles knockback as if it were calculated clientside, ensuring that no player is at a disadvantage,
|
||||
regardless of their own or their opponent’s connection.
|
||||
|
||||
Showcase: https://www.youtube.com/watch?v=SVokpr3v-TA
|
||||
|
||||
Official Discord: https://discord.gg/nnpqpAtyVW
|
||||
|
||||
## Frequently Asked Questions (FAQ)
|
||||
|
||||
### Does this change put high ping players at a disadvantage?
|
||||
@@ -14,7 +18,7 @@ regardless of their own or their opponent’s connection.
|
||||
**Knockback control.** For example, it will be easier to escape crit chains and punish crit.
|
||||
|
||||
### Why was the configurability of ping offset removed?
|
||||
**It promotes consistency across all servers.** Extensive testing with top players has shown that an offset of 20 provides a balanced experience for everyone.
|
||||
**It promotes consistency across all servers.** Extensive testing with top players has shown that an offset of 25 provides a balanced experience for everyone.
|
||||
|
||||
### How do I change the ping offset?
|
||||
**You must run a modified build of KnockbackSync.** The variable can be changed inside of the [PlayerData](src/main/java/me/caseload/knockbacksync/manager/PlayerData.java) class.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>me.caseload</groupId>
|
||||
<artifactId>KnockbackSync</artifactId>
|
||||
<version>1.3</version>
|
||||
<version>1.3.2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>KnockbackSync</name>
|
||||
@@ -47,6 +47,22 @@
|
||||
<pattern>dev.jorel.commandapi</pattern>
|
||||
<shadedPattern>me.caseload.knockbacksync.commandapi</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.github.retrooper</pattern>
|
||||
<shadedPattern>me.caseload.knockbacksync.retrooper</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.projectlombok</pattern>
|
||||
<shadedPattern>me.caseload.knockbacksync.projectlombok</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.kohsuke</pattern>
|
||||
<shadedPattern>me.caseload.knockbacksync.kohsuke</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.bstats</pattern>
|
||||
<shadedPattern>me.caseload.knockbacksync.bstats</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
</plugin>
|
||||
@@ -68,6 +84,10 @@
|
||||
<id>codemc-releases</id>
|
||||
<url>https://repo.codemc.io/repository/maven-releases/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>opencollab</id>
|
||||
<url>https://repo.opencollab.dev/maven-snapshots/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
@@ -98,5 +118,16 @@
|
||||
<artifactId>github-api</artifactId>
|
||||
<version>1.326</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.geysermc.floodgate</groupId>
|
||||
<artifactId>api</artifactId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bstats</groupId>
|
||||
<artifactId>bstats-bukkit</artifactId>
|
||||
<version>3.0.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -5,27 +5,28 @@ import dev.jorel.commandapi.CommandAPI;
|
||||
import dev.jorel.commandapi.CommandAPIBukkitConfig;
|
||||
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import me.caseload.knockbacksync.command.MainCommand;
|
||||
import me.caseload.knockbacksync.listener.*;
|
||||
import me.caseload.knockbacksync.runnable.PingRunnable;
|
||||
import me.caseload.knockbacksync.manager.ConfigManager;
|
||||
import me.caseload.knockbacksync.stats.BuildTypePie;
|
||||
import me.caseload.knockbacksync.stats.PlayerVersionsPie;
|
||||
import me.caseload.knockbacksync.stats.StatsManager;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.kohsuke.github.GitHub;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public final class KnockbackSync extends JavaPlugin {
|
||||
|
||||
private boolean toggled;
|
||||
private boolean runnable;
|
||||
private boolean updateAvailable;
|
||||
public static Logger LOGGER;
|
||||
public static JavaPlugin INSTANCE;
|
||||
|
||||
private long runnableInterval;
|
||||
private long combatTimer;
|
||||
private long spikeThreshold;
|
||||
@Getter
|
||||
private final ConfigManager configManager = new ConfigManager();
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
@@ -36,8 +37,12 @@ public final class KnockbackSync extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
LOGGER = getLogger();
|
||||
INSTANCE = this;
|
||||
checkForUpdates();
|
||||
|
||||
saveDefaultConfig();
|
||||
configManager.loadConfig(false);
|
||||
|
||||
CommandAPI.onEnable();
|
||||
new MainCommand().register();
|
||||
@@ -48,20 +53,19 @@ public final class KnockbackSync extends JavaPlugin {
|
||||
new PlayerJoinQuitListener()
|
||||
);
|
||||
|
||||
PacketEvents.getAPI().getEventManager().registerListener(
|
||||
PacketEvents.getAPI().getEventManager().registerListeners(
|
||||
new AttributeChangeListener(),
|
||||
new PingReceiveListener()
|
||||
);
|
||||
|
||||
PacketEvents.getAPI().getSettings()
|
||||
.checkForUpdates(false)
|
||||
.debug(false);
|
||||
|
||||
PacketEvents.getAPI().load();
|
||||
PacketEvents.getAPI().init();
|
||||
|
||||
toggled = getConfig().getBoolean("enabled", true);
|
||||
runnable = getConfig().getBoolean("runnable.enabled", true);
|
||||
runnableInterval = getConfig().getLong("runnable.interval", 5L);
|
||||
combatTimer = getConfig().getLong("runnable.timer", 30L);
|
||||
spikeThreshold = getConfig().getLong("spike_threshold", 25L);
|
||||
|
||||
if (runnable)
|
||||
new PingRunnable().runTaskTimerAsynchronously(this, 0L, runnableInterval);
|
||||
StatsManager.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,14 +98,14 @@ public final class KnockbackSync extends JavaPlugin {
|
||||
boolean updateAvailable = !currentVersion.equalsIgnoreCase(latestVersion);
|
||||
|
||||
if (updateAvailable) {
|
||||
getLogger().warning("A new update is available for download at: https://github.com/CASELOAD7000/knockback-sync/releases/latest");
|
||||
LOGGER.warning("A new update is available for download at: https://github.com/CASELOAD7000/knockback-sync/releases/latest");
|
||||
} else {
|
||||
getLogger().info("You are running the latest release.");
|
||||
LOGGER.info("You are running the latest release.");
|
||||
}
|
||||
|
||||
setUpdateAvailable(updateAvailable);
|
||||
configManager.setUpdateAvailable(updateAvailable);
|
||||
} catch (Exception e) {
|
||||
getLogger().severe("Failed to check for updates: " + e.getMessage());
|
||||
LOGGER.severe("Failed to check for updates: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package me.caseload.knockbacksync.command;
|
||||
|
||||
import dev.jorel.commandapi.CommandAPICommand;
|
||||
import me.caseload.knockbacksync.command.subcommand.PingSubcommand;
|
||||
import me.caseload.knockbacksync.command.subcommand.ReloadSubcommand;
|
||||
import me.caseload.knockbacksync.command.subcommand.StatusSubcommand;
|
||||
import me.caseload.knockbacksync.command.subcommand.ToggleSubcommand;
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
@@ -12,6 +14,8 @@ public class MainCommand {
|
||||
.withAliases("kbsync")
|
||||
.withSubcommand(new PingSubcommand().getCommand())
|
||||
.withSubcommand(new ToggleSubcommand().getCommand())
|
||||
.withSubcommand(new ReloadSubcommand().getCommand())
|
||||
.withSubcommand(new StatusSubcommand().getCommand())
|
||||
.executes((sender, args) -> {
|
||||
sender.sendMessage(ChatColor.translateAlternateColorCodes(
|
||||
'&',
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.caseload.knockbacksync.command.subcommand;
|
||||
|
||||
import dev.jorel.commandapi.CommandAPICommand;
|
||||
import me.caseload.knockbacksync.KnockbackSync;
|
||||
import me.caseload.knockbacksync.manager.ConfigManager;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
public class ReloadSubcommand implements Listener {
|
||||
|
||||
public CommandAPICommand getCommand() {
|
||||
return new CommandAPICommand("reload")
|
||||
.withPermission("knockbacksync.reload")
|
||||
.executes((sender, args) -> {
|
||||
ConfigManager configManager = KnockbackSync.getInstance().getConfigManager();
|
||||
configManager.loadConfig(true);
|
||||
|
||||
String reloadMessage = ChatColor.translateAlternateColorCodes('&',
|
||||
configManager.getReloadMessage()
|
||||
);
|
||||
|
||||
sender.sendMessage(reloadMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package me.caseload.knockbacksync.command.subcommand;
|
||||
|
||||
import dev.jorel.commandapi.CommandAPICommand;
|
||||
import dev.jorel.commandapi.CommandPermission;
|
||||
import dev.jorel.commandapi.arguments.PlayerArgument;
|
||||
import me.caseload.knockbacksync.KnockbackSync;
|
||||
import me.caseload.knockbacksync.manager.ConfigManager;
|
||||
import me.caseload.knockbacksync.manager.PlayerDataManager;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class StatusSubcommand implements Listener {
|
||||
|
||||
public CommandAPICommand getCommand() {
|
||||
return new CommandAPICommand("status")
|
||||
.withPermission(CommandPermission.NONE)
|
||||
.withOptionalArguments(new PlayerArgument("target"))
|
||||
.executes((sender, args) -> {
|
||||
ConfigManager configManager = KnockbackSync.getInstance().getConfigManager();
|
||||
Player target = (Player) args.get("target");
|
||||
|
||||
// Show global status
|
||||
boolean globalStatus = configManager.isToggled();
|
||||
sender.sendMessage(ChatColor.YELLOW + "Global KnockbackSync status: " +
|
||||
(globalStatus ? ChatColor.GREEN + "Enabled" : ChatColor.RED + "Disabled"));
|
||||
|
||||
// Show player status
|
||||
if (target == null && sender instanceof Player)
|
||||
target = (Player) sender;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
if (sender.hasPermission("knockbacksync.status.self") ||
|
||||
(sender.hasPermission("knockbacksync.status.other") && !sender.equals(target))) {
|
||||
|
||||
UUID uuid = target.getUniqueId();
|
||||
boolean playerStatus = !PlayerDataManager.containsPlayerData(uuid);
|
||||
|
||||
if (!globalStatus) {
|
||||
sender.sendMessage(ChatColor.YELLOW + target.getName() + "'s KnockbackSync status: " +
|
||||
ChatColor.RED + "Disabled (Global toggle is off)");
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.YELLOW + target.getName() + "'s KnockbackSync status: " +
|
||||
(playerStatus ? ChatColor.GREEN + "Enabled" : ChatColor.RED + "Disabled"));
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "You don't have the "
|
||||
+ (sender.equals(target) ? "knockbacksync.status.self" : "knockbacksync.status.other") +
|
||||
" permission needed to check status for " +
|
||||
(sender.equals(target) ? "yourself" : "other players") + ".");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,86 @@
|
||||
package me.caseload.knockbacksync.command.subcommand;
|
||||
|
||||
import dev.jorel.commandapi.CommandAPICommand;
|
||||
import dev.jorel.commandapi.CommandPermission;
|
||||
import dev.jorel.commandapi.arguments.PlayerArgument;
|
||||
import me.caseload.knockbacksync.KnockbackSync;
|
||||
import me.caseload.knockbacksync.manager.ConfigManager;
|
||||
import me.caseload.knockbacksync.manager.PlayerData;
|
||||
import me.caseload.knockbacksync.manager.PlayerDataManager;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
public class ToggleSubcommand {
|
||||
import java.util.UUID;
|
||||
|
||||
public class ToggleSubcommand implements Listener {
|
||||
|
||||
public CommandAPICommand getCommand() {
|
||||
return new CommandAPICommand("toggle")
|
||||
.withPermission("knockbacksync.toggle")
|
||||
.withPermission(CommandPermission.NONE)
|
||||
.withOptionalArguments(new PlayerArgument("target"))
|
||||
.executes((sender, args) -> {
|
||||
boolean toggledState = !KnockbackSync.getInstance().isToggled();
|
||||
ConfigManager configManager = KnockbackSync.getInstance().getConfigManager();
|
||||
Player target = (Player) args.get("target");
|
||||
String message;
|
||||
|
||||
KnockbackSync.getInstance().getConfig().set("enabled", toggledState);
|
||||
KnockbackSync.getInstance().saveConfig();
|
||||
KnockbackSync.getInstance().setToggled(toggledState);
|
||||
if (target == null) {
|
||||
// Global toggle
|
||||
if (sender.hasPermission("knockbacksync.toggle.global")) {
|
||||
boolean toggledState = !configManager.isToggled();
|
||||
configManager.setToggled(toggledState);
|
||||
|
||||
String message = ChatColor.translateAlternateColorCodes('&',
|
||||
toggledState ?
|
||||
KnockbackSync.getInstance().getConfig().getString("enable_message", "&aSuccessfully enabled KnockbackSync.") :
|
||||
KnockbackSync.getInstance().getConfig().getString("disable_message", "&cSuccessfully disabled KnockbackSync.")
|
||||
);
|
||||
KnockbackSync.getInstance().getConfig().set("enabled", toggledState);
|
||||
KnockbackSync.getInstance().saveConfig();
|
||||
|
||||
sender.sendMessage(message);
|
||||
message = ChatColor.translateAlternateColorCodes('&',
|
||||
toggledState ? configManager.getEnableMessage() : configManager.getDisableMessage()
|
||||
);
|
||||
} else {
|
||||
message = ChatColor.RED + "You don't have permission to toggle the global setting.";
|
||||
}
|
||||
sender.sendMessage(message);
|
||||
} else {
|
||||
// Player-specific toggle
|
||||
if (!configManager.isToggled()) {
|
||||
message = ChatColor.RED + "Knockbacksync is currently disabled on this server. Contact your server administrator for more information.";
|
||||
sender.sendMessage(message);
|
||||
} else if (sender instanceof Player && sender.equals(target) && sender.hasPermission("knockbacksync.toggle.self")) {
|
||||
togglePlayerKnockback(target, configManager, sender);
|
||||
} else if (sender.hasPermission("knockbacksync.toggle.other")) {
|
||||
togglePlayerKnockback(target, configManager, sender);
|
||||
} else {
|
||||
message = ChatColor.RED + "You don't have permission to toggle knockback for " +
|
||||
(sender.equals(target) ? "yourself" : "other players") + ".";
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void togglePlayerKnockback(Player target, ConfigManager configManager, CommandSender sender) {
|
||||
UUID uuid = target.getUniqueId();
|
||||
|
||||
if (PlayerDataManager.shouldExempt(uuid)) {
|
||||
String message = ChatColor.translateAlternateColorCodes('&',
|
||||
configManager.getPlayerIneligibleMessage()
|
||||
).replace("%player%", target.getName());
|
||||
|
||||
sender.sendMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasPlayerData = PlayerDataManager.containsPlayerData(uuid);
|
||||
if (hasPlayerData)
|
||||
PlayerDataManager.removePlayerData(uuid);
|
||||
else
|
||||
PlayerDataManager.addPlayerData(uuid, new PlayerData(target));
|
||||
|
||||
String message = ChatColor.translateAlternateColorCodes('&',
|
||||
hasPlayerData ? configManager.getPlayerDisableMessage() : configManager.getPlayerEnableMessage()
|
||||
).replace("%player%", target.getName());
|
||||
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package me.caseload.knockbacksync.listener;
|
||||
|
||||
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUpdateAttributes;
|
||||
import me.caseload.knockbacksync.manager.PlayerData;
|
||||
import me.caseload.knockbacksync.manager.PlayerDataManager;
|
||||
import me.caseload.knockbacksync.util.MathUtil;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AttributeChangeListener extends PacketListenerAbstract {
|
||||
|
||||
public static final UUID SPRINTING_MODIFIER_UUID =
|
||||
UUID.fromString("662A6B8D-DA3E-4C1C-8813-96EA6097278D");
|
||||
|
||||
double minGravity = -1;
|
||||
double maxGravity = 1;
|
||||
double defaultGravity = 0.08;
|
||||
double currentGravity;
|
||||
|
||||
public AttributeChangeListener() {
|
||||
currentGravity = defaultGravity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(PacketSendEvent event) {
|
||||
// Intercept the packet that updates entity properties (attributes)
|
||||
if (event.getPacketType() != PacketType.Play.Server.UPDATE_ATTRIBUTES)
|
||||
return;
|
||||
|
||||
WrapperPlayServerUpdateAttributes packet = new WrapperPlayServerUpdateAttributes(event);
|
||||
|
||||
// Check if the entity is a player
|
||||
Player player = event.getPlayer();
|
||||
if (player == null || !PlayerDataManager.containsPlayerData(player.getUniqueId()))
|
||||
return;
|
||||
|
||||
// Get the attributes from the packet
|
||||
for (WrapperPlayServerUpdateAttributes.Property property : packet.getProperties()) {
|
||||
// You can now check for specific attributes
|
||||
if (property.getAttribute().equals(Attributes.GENERIC_GRAVITY)) {
|
||||
onPlayerGravityChange(player, calculateValueWithModifiers(property));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double calculateValueWithModifiers(WrapperPlayServerUpdateAttributes.Property property) {
|
||||
double baseValue = property.getValue();
|
||||
double additionSum = 0;
|
||||
double multiplyBaseSum = 0;
|
||||
double multiplyTotalProduct = 1.0;
|
||||
|
||||
List<WrapperPlayServerUpdateAttributes.PropertyModifier> modifiers = property.getModifiers();
|
||||
// TODO, account for https://bugs.mojang.com/browse/MC-69459 ?
|
||||
// modifiers.removeIf(modifier -> modifier.getUUID().equals(SPRINTING_MODIFIER_UUID) || modifier.getName().getKey().equals("sprinting"));
|
||||
|
||||
for (WrapperPlayServerUpdateAttributes.PropertyModifier modifier : modifiers) {
|
||||
switch (modifier.getOperation()) {
|
||||
case ADDITION:
|
||||
additionSum += modifier.getAmount();
|
||||
break;
|
||||
case MULTIPLY_BASE:
|
||||
multiplyBaseSum += modifier.getAmount();
|
||||
break;
|
||||
case MULTIPLY_TOTAL:
|
||||
multiplyTotalProduct *= (1.0 + modifier.getAmount());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
double newValue = (baseValue + additionSum) * (1 + multiplyBaseSum) * multiplyTotalProduct;
|
||||
newValue = MathUtil.clamp(newValue, minGravity, maxGravity);
|
||||
|
||||
if (newValue < minGravity || newValue > maxGravity)
|
||||
throw new IllegalArgumentException("New value must be between min and max!");
|
||||
|
||||
return this.currentGravity = newValue;
|
||||
}
|
||||
|
||||
// Yes this is not properly latency compensated, that would require including a proper simulation engine
|
||||
// Laggy players will just have to deal with being on the wrong gravity for a few hundred ms, too bad!
|
||||
public void onPlayerGravityChange(Player player, double newGravity) {
|
||||
PlayerData playerData = PlayerDataManager.getPlayerData(player.getUniqueId());
|
||||
if (playerData.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_20_5)) {
|
||||
playerData.setGravity(newGravity);
|
||||
} else {
|
||||
currentGravity = defaultGravity;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public class PingReceiveListener extends PacketListenerAbstract {
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (!KnockbackSync.getInstance().isToggled())
|
||||
if (!KnockbackSync.getInstance().getConfigManager().isToggled())
|
||||
return;
|
||||
|
||||
if (event.getPacketType() != PacketType.Play.Client.PONG)
|
||||
@@ -22,6 +22,12 @@ public class PingReceiveListener extends PacketListenerAbstract {
|
||||
Player player = event.getPlayer();
|
||||
PlayerData playerData = PlayerDataManager.getPlayerData(player.getUniqueId());
|
||||
|
||||
// If player is timed out by the server and removed from the map on the main thread
|
||||
// The server can still receive ping packets from the disconnected client
|
||||
// At which point the entry will no longer be in the map but this code will be processed!
|
||||
if (playerData == null)
|
||||
return;
|
||||
|
||||
int packetId = new WrapperPlayClientPong(event).getId();
|
||||
|
||||
Long sendTime = playerData.getTimeline().get(packetId);
|
||||
|
||||
@@ -12,17 +12,21 @@ public class PlayerDamageListener implements Listener {
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerDamage(EntityDamageByEntityEvent event) {
|
||||
if (!KnockbackSync.getInstance().isToggled())
|
||||
if (!KnockbackSync.getInstance().getConfigManager().isToggled())
|
||||
return;
|
||||
|
||||
if (!(event.getEntity() instanceof Player victim) || !(event.getDamager() instanceof Player attacker))
|
||||
return;
|
||||
|
||||
PlayerData playerData = PlayerDataManager.getPlayerData(victim.getUniqueId());
|
||||
if (playerData == null)
|
||||
return;
|
||||
|
||||
playerData.setVerticalVelocity(playerData.calculateVerticalVelocity(attacker)); // do not move this calculation
|
||||
playerData.setLastDamageTicks(victim.getNoDamageTicks());
|
||||
playerData.updateCombat();
|
||||
|
||||
if (!KnockbackSync.getInstance().isRunnable())
|
||||
if (!KnockbackSync.getInstance().getConfigManager().isRunnableEnabled())
|
||||
playerData.sendPing();
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ public class PlayerJoinQuitListener implements Listener {
|
||||
Player player = event.getPlayer();
|
||||
PlayerDataManager.addPlayerData(player.getUniqueId(), new PlayerData(player));
|
||||
|
||||
if (KnockbackSync.getInstance().isUpdateAvailable() && player.hasPermission("knockbacksync.update"))
|
||||
if (KnockbackSync.getInstance().getConfigManager().isUpdateAvailable() && KnockbackSync.getInstance().getConfigManager().isNotifyUpdate() && player.hasPermission("knockbacksync.update"))
|
||||
player.sendMessage(ChatColor.translateAlternateColorCodes(
|
||||
'&',
|
||||
"&6An updated version of &eKnockbackSync &6is now available for download at: &bhttps://github.com/CASELOAD7000/knockback-sync/releases/latest"
|
||||
@@ -30,6 +30,8 @@ public class PlayerJoinQuitListener implements Listener {
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
UUID uuid = event.getPlayer().getUniqueId();
|
||||
PlayerData playerData = PlayerDataManager.getPlayerData(uuid);
|
||||
if (playerData == null)
|
||||
return;
|
||||
|
||||
if (playerData.isInCombat())
|
||||
playerData.quitCombat(true);
|
||||
|
||||
@@ -17,12 +17,10 @@ public class PlayerKnockbackListener implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onPlayerVelocity(PlayerVelocityEvent event) {
|
||||
if (!KnockbackSync.getInstance().isToggled())
|
||||
if (!KnockbackSync.getInstance().getConfigManager().isToggled())
|
||||
return;
|
||||
|
||||
Player victim = event.getPlayer();
|
||||
PlayerData playerData = PlayerDataManager.getPlayerData(victim.getUniqueId());
|
||||
|
||||
EntityDamageEvent entityDamageEvent = victim.getLastDamageCause();
|
||||
if (entityDamageEvent == null)
|
||||
return;
|
||||
@@ -35,20 +33,23 @@ public class PlayerKnockbackListener implements Listener {
|
||||
if (!(attacker instanceof Player))
|
||||
return;
|
||||
|
||||
PlayerData playerData = PlayerDataManager.getPlayerData(victim.getUniqueId());
|
||||
if (playerData == null)
|
||||
return;
|
||||
|
||||
Integer damageTicks = playerData.getLastDamageTicks();
|
||||
if (damageTicks != null && damageTicks > 8)
|
||||
return;
|
||||
|
||||
Vector velocity = victim.getVelocity();
|
||||
Double verticalVelocity = playerData.getVerticalVelocity();
|
||||
if (verticalVelocity == null || !playerData.isOnGround(velocity.getY()))
|
||||
return;
|
||||
|
||||
Long lastAdjustment = playerData.getLastAdjustment();
|
||||
if (lastAdjustment != null && System.currentTimeMillis() - lastAdjustment < 250)
|
||||
return;
|
||||
|
||||
Vector adjustedVelocity = velocity.clone().setY(
|
||||
verticalVelocity
|
||||
);
|
||||
|
||||
victim.setVelocity(adjustedVelocity);
|
||||
playerData.setLastAdjustment(System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package me.caseload.knockbacksync.manager;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import me.caseload.knockbacksync.KnockbackSync;
|
||||
import me.caseload.knockbacksync.runnable.PingRunnable;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class ConfigManager {
|
||||
|
||||
private boolean toggled;
|
||||
private boolean runnableEnabled;
|
||||
private boolean updateAvailable;
|
||||
private boolean notifyUpdate;
|
||||
|
||||
private long runnableInterval;
|
||||
private long combatTimer;
|
||||
private long spikeThreshold;
|
||||
|
||||
private String enableMessage;
|
||||
private String disableMessage;
|
||||
private String playerEnableMessage;
|
||||
private String playerDisableMessage;
|
||||
private String playerIneligibleMessage;
|
||||
private String reloadMessage;
|
||||
|
||||
private BukkitTask pingTask;
|
||||
|
||||
public void loadConfig(boolean reloadConfig) {
|
||||
KnockbackSync instance = KnockbackSync.getInstance();
|
||||
|
||||
if (reloadConfig)
|
||||
instance.reloadConfig();
|
||||
|
||||
toggled = instance.getConfig().getBoolean("enabled", true);
|
||||
|
||||
// Checks to see if the runnable was enabled...
|
||||
// and if we now want to disable it
|
||||
boolean newRunnableEnabled = instance.getConfig().getBoolean("runnable.enabled", true);
|
||||
if (runnableEnabled && newRunnableEnabled && pingTask != null) // null check for first startup
|
||||
pingTask.cancel();
|
||||
|
||||
runnableEnabled = newRunnableEnabled;
|
||||
|
||||
if (runnableEnabled)
|
||||
pingTask = new PingRunnable().runTaskTimerAsynchronously(instance, 0L, runnableInterval);
|
||||
|
||||
notifyUpdate = instance.getConfig().getBoolean("notify_updates", true);
|
||||
runnableInterval = instance.getConfig().getLong("runnable.interval", 5L);
|
||||
combatTimer = instance.getConfig().getLong("runnable.timer", 30L);
|
||||
spikeThreshold = instance.getConfig().getLong("spike_threshold", 20L);
|
||||
enableMessage = instance.getConfig().getString("enable_message", "&aSuccessfully enabled KnockbackSync.");
|
||||
disableMessage = instance.getConfig().getString("disable_message", "&cSuccessfully disabled KnockbackSync.");
|
||||
playerEnableMessage = instance.getConfig().getString("player_enable_message", "&aSuccessfully enabled KnockbackSync for %player%.");
|
||||
playerDisableMessage = instance.getConfig().getString("player_disable_message", "&cSuccessfully disabled KnockbackSync for %player%.");
|
||||
playerIneligibleMessage = instance.getConfig().getString("player_ineligible_message", "&c%player% is ineligible for KnockbackSync. If you believe this is an error, please open an issue on the github page.");
|
||||
reloadMessage = instance.getConfig().getString("reload_message", "&aSuccessfully reloaded KnockbackSync.");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package me.caseload.knockbacksync.manager;
|
||||
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.protocol.player.User;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPing;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
@@ -28,8 +30,10 @@ public class PlayerData {
|
||||
|
||||
private final Player player;
|
||||
|
||||
public final User user;
|
||||
|
||||
// Please read the GitHub FAQ before adjusting.
|
||||
private static final long PING_OFFSET = 20;
|
||||
private static final long PING_OFFSET = 25;
|
||||
|
||||
@NotNull
|
||||
private final Map<Integer, Long> timeline = new HashMap<>();
|
||||
@@ -41,13 +45,20 @@ public class PlayerData {
|
||||
private BukkitTask combatTask;
|
||||
|
||||
@Nullable @Setter
|
||||
private Long ping, previousPing, lastAdjustment;
|
||||
private Long ping, previousPing;
|
||||
|
||||
@Nullable @Setter
|
||||
private Double verticalVelocity;
|
||||
|
||||
@Nullable @Setter
|
||||
private Integer lastDamageTicks;
|
||||
|
||||
@Setter
|
||||
private double gravity = 0.08;
|
||||
|
||||
public PlayerData(Player player) {
|
||||
this.player = player;
|
||||
this.user = PacketEvents.getAPI().getPlayerManager().getUser(player);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,7 +71,7 @@ public class PlayerData {
|
||||
public long getEstimatedPing() {
|
||||
long currentPing = (ping != null) ? ping : player.getPing();
|
||||
long lastPing = (previousPing != null) ? previousPing : player.getPing();
|
||||
long ping = (currentPing - lastPing > KnockbackSync.getInstance().getSpikeThreshold()) ? lastPing : currentPing;
|
||||
long ping = (currentPing - lastPing > KnockbackSync.getInstance().getConfigManager().getSpikeThreshold()) ? lastPing : currentPing;
|
||||
|
||||
return Math.max(1, ping - PING_OFFSET);
|
||||
}
|
||||
@@ -103,9 +114,12 @@ public class PlayerData {
|
||||
if (gDist <= 0)
|
||||
return false; // prevent player from taking adjusted knockback when on ground serverside
|
||||
|
||||
int tMax = verticalVelocity > 0 ? MathUtil.calculateTimeToMaxVelocity(verticalVelocity) : 0;
|
||||
double mH = verticalVelocity > 0 ? MathUtil.calculateDistanceTraveled(verticalVelocity, tMax) : 0;
|
||||
int tFall = MathUtil.calculateFallTime(verticalVelocity, mH + gDist);
|
||||
int tMax = verticalVelocity > 0 ? MathUtil.calculateTimeToMaxVelocity(verticalVelocity, gravity) : 0;
|
||||
double mH = verticalVelocity > 0 ? MathUtil.calculateDistanceTraveled(verticalVelocity, tMax, gravity) : 0;
|
||||
int tFall = MathUtil.calculateFallTime(verticalVelocity, mH + gDist, gravity);
|
||||
|
||||
if (tFall == -1)
|
||||
return false; // reached the max tick limit, not safe to predict
|
||||
|
||||
return getEstimatedPing() >= tMax + tFall / 20.0 * 1000 && gDist <= 1.3;
|
||||
}
|
||||
@@ -213,6 +227,15 @@ public class PlayerData {
|
||||
@NotNull
|
||||
private BukkitTask newCombatTask() {
|
||||
return Bukkit.getScheduler().runTaskLaterAsynchronously(KnockbackSync.getInstance(),
|
||||
() -> quitCombat(false), KnockbackSync.getInstance().getCombatTimer());
|
||||
() -> quitCombat(false), KnockbackSync.getInstance().getConfigManager().getCombatTimer());
|
||||
}
|
||||
|
||||
public ClientVersion getClientVersion() {
|
||||
ClientVersion ver = user.getClientVersion();
|
||||
if (ver == null) {
|
||||
// If temporarily null, assume server version...
|
||||
return ClientVersion.getById(PacketEvents.getAPI().getServerManager().getVersion().getProtocolVersion());
|
||||
}
|
||||
return ver;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,42 @@
|
||||
package me.caseload.knockbacksync.manager;
|
||||
|
||||
import io.github.retrooper.packetevents.util.GeyserUtil;
|
||||
import me.caseload.knockbacksync.util.FloodgateUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class PlayerDataManager {
|
||||
|
||||
private static final Map<UUID, PlayerData> playerDataMap = new HashMap<>();
|
||||
private static final Map<UUID, PlayerData> playerDataMap = new ConcurrentHashMap<>();
|
||||
|
||||
public static @NotNull PlayerData getPlayerData(UUID uuid) {
|
||||
public static PlayerData getPlayerData(@NotNull UUID uuid) {
|
||||
return playerDataMap.get(uuid);
|
||||
}
|
||||
|
||||
public static void addPlayerData(UUID uuid, PlayerData playerData) {
|
||||
playerDataMap.put(uuid, playerData);
|
||||
public static void addPlayerData(@NotNull UUID uuid, @NotNull PlayerData playerData) {
|
||||
if (!shouldExempt(uuid))
|
||||
playerDataMap.put(uuid, playerData);
|
||||
}
|
||||
|
||||
public static void removePlayerData(UUID uuid) {
|
||||
public static void removePlayerData(@NotNull UUID uuid) {
|
||||
playerDataMap.remove(uuid);
|
||||
}
|
||||
|
||||
public static boolean containsPlayerData(@NotNull UUID uuid) {
|
||||
return playerDataMap.containsKey(uuid);
|
||||
}
|
||||
|
||||
public static boolean shouldExempt(@NotNull UUID uuid) {
|
||||
// Geyser players don't have Java movement
|
||||
return GeyserUtil.isGeyserPlayer(uuid)
|
||||
// Floodgate is the authentication system for Geyser on servers that use Geyser as a proxy instead of installing it as a plugin directly on the server
|
||||
|| FloodgateUtil.isFloodgatePlayer(uuid)
|
||||
// Geyser formatted player string
|
||||
// This will never happen for Java players, as the first character in the 3rd group is always 4 (xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx)
|
||||
|| uuid.toString().startsWith("00000000-0000-0000-0009");
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ public class PingRunnable extends BukkitRunnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!KnockbackSync.getInstance().isToggled())
|
||||
if (!KnockbackSync.getInstance().getConfigManager().isToggled())
|
||||
return;
|
||||
|
||||
for (UUID uuid : CombatManager.getPlayers()) {
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package me.caseload.knockbacksync.stats;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import me.caseload.knockbacksync.KnockbackSync;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.kohsuke.github.GHAsset;
|
||||
import org.kohsuke.github.GHRelease;
|
||||
import org.kohsuke.github.GitHub;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.List;
|
||||
|
||||
public class BuildTypePie extends SimplePie {
|
||||
|
||||
private static final String RELEASES_FILE = "releases.txt";
|
||||
private static final String DEV_BUILDS_FILE = "dev-builds.txt";
|
||||
private static final File dataFolder = KnockbackSync.INSTANCE.getDataFolder();
|
||||
private static String cachedBuildType = null;
|
||||
|
||||
public BuildTypePie() {
|
||||
super("build_type", BuildTypePie::determineBuildType);
|
||||
}
|
||||
|
||||
public static String determineBuildType() {
|
||||
if (cachedBuildType == null) {
|
||||
cachedBuildType = calculateBuildType();
|
||||
}
|
||||
return cachedBuildType;
|
||||
}
|
||||
|
||||
private static String calculateBuildType() {
|
||||
try {
|
||||
String currentHash = getPluginJarHash();
|
||||
downloadBuildFiles();
|
||||
|
||||
if (isHashInFile(currentHash, new File(dataFolder, RELEASES_FILE))) {
|
||||
return "release";
|
||||
} else if (isHashInFile(currentHash, new File(dataFolder, DEV_BUILDS_FILE))) {
|
||||
return "dev";
|
||||
} else {
|
||||
return "fork";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadBuildFiles() throws IOException {
|
||||
GitHub gitHub = GitHub.connectAnonymously();
|
||||
GHRelease latestRelease = gitHub.getRepository("Axionize/knockback-sync")
|
||||
.getLatestRelease();
|
||||
List<GHAsset> assets = latestRelease.listAssets().toList();
|
||||
for (GHAsset asset : assets) {
|
||||
if (asset.getName().equals(RELEASES_FILE) || asset.getName().equals(DEV_BUILDS_FILE)) {
|
||||
KnockbackSync.INSTANCE.getLogger().info("Downloading: " + asset.getName());
|
||||
|
||||
String jsonContent = readStringFromURL(asset.getUrl().toString());
|
||||
JsonObject jsonObject = JsonParser.parseString(jsonContent).getAsJsonObject();
|
||||
String downloadUrl = jsonObject.get("browser_download_url").getAsString();
|
||||
|
||||
try (InputStream inputStream = new URL(downloadUrl).openStream();
|
||||
FileOutputStream outputStream = new FileOutputStream(new File(dataFolder, asset.getName()))) {
|
||||
inputStream.transferTo(outputStream);
|
||||
}
|
||||
|
||||
KnockbackSync.INSTANCE.getLogger().info("Downloaded: " + asset.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isHashInFile(String hash, File file) throws IOException {
|
||||
if (!file.exists()) {
|
||||
return false;
|
||||
}
|
||||
List<String> lines = Files.readAllLines(Paths.get(file.getPath()));
|
||||
return lines.contains(hash);
|
||||
}
|
||||
|
||||
private static String getPluginJarHash() throws Exception {
|
||||
URL jarUrl = Bukkit.getPluginManager().getPlugin("KnockbackSync").getClass().getProtectionDomain().getCodeSource().getLocation();
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
try (InputStream is = jarUrl.openStream()) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int read;
|
||||
while ((read = is.read(buffer)) > 0) {
|
||||
digest.update(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
byte[] hash = digest.digest();
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
for (byte b : hash) {
|
||||
String hex = Integer.toHexString(0xff & b);
|
||||
if (hex.length() == 1) hexString.append('0');
|
||||
hexString.append(hex);
|
||||
}
|
||||
return hexString.toString();
|
||||
}
|
||||
|
||||
private static String readStringFromURL(String urlString) throws IOException {
|
||||
try (InputStream inputStream = new URL(urlString).openStream()) {
|
||||
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.caseload.knockbacksync.stats;
|
||||
|
||||
import me.caseload.knockbacksync.manager.PlayerData;
|
||||
import me.caseload.knockbacksync.manager.PlayerDataManager;
|
||||
import org.bstats.charts.AdvancedPie;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class PlayerVersionsPie extends AdvancedPie {
|
||||
|
||||
// Gets the client versions of players online
|
||||
public PlayerVersionsPie() {
|
||||
super("player_version", () -> {
|
||||
Map<String, Integer> valueMap = new HashMap<>();
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
PlayerData playerData = PlayerDataManager.getPlayerData(player.getUniqueId());
|
||||
valueMap.put(playerData.getClientVersion().toString(), valueMap.getOrDefault(playerData.getClientVersion().toString(), 0) + 1);
|
||||
}
|
||||
return valueMap;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package me.caseload.knockbacksync.stats;
|
||||
|
||||
import me.caseload.knockbacksync.KnockbackSync;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
public class StatsManager {
|
||||
|
||||
public static Metrics metrics;
|
||||
|
||||
public static void init() {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(KnockbackSync.getInstance(), () -> {
|
||||
BuildTypePie.determineBuildType(); // Function to calculate hash
|
||||
metrics = new Metrics(KnockbackSync.INSTANCE, 23568);
|
||||
metrics.addCustomChart(new PlayerVersionsPie());
|
||||
metrics.addCustomChart(new BuildTypePie());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package me.caseload.knockbacksync.util;
|
||||
|
||||
import org.geysermc.floodgate.api.FloodgateApi;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class FloodgateUtil {
|
||||
|
||||
private static boolean CHECKED_FOR_FLOODGATE;
|
||||
private static boolean FLOODGATE_PRESENT;
|
||||
|
||||
public static boolean isFloodgatePlayer(UUID uuid) {
|
||||
if (!CHECKED_FOR_FLOODGATE) {
|
||||
try {
|
||||
Class.forName("org.geysermc.floodgate.api.FloodgateApi");
|
||||
FLOODGATE_PRESENT = true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
FLOODGATE_PRESENT = false;
|
||||
}
|
||||
CHECKED_FOR_FLOODGATE = true;
|
||||
}
|
||||
|
||||
if (FLOODGATE_PRESENT) {
|
||||
return FloodgateApi.getInstance().isFloodgatePlayer(uuid);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,29 +3,32 @@ package me.caseload.knockbacksync.util;
|
||||
public class MathUtil {
|
||||
|
||||
private static final double TERMINAL_VELOCITY = 3.92;
|
||||
private static final double GRAVITY = 0.08;
|
||||
private static final double MULTIPLIER = 0.98;
|
||||
private static final int MAX_TICKS = 20;
|
||||
|
||||
public static double calculateDistanceTraveled(double velocity, int time)
|
||||
public static double calculateDistanceTraveled(double velocity, int time, double acceleration)
|
||||
{
|
||||
double totalDistance = 0;
|
||||
|
||||
for (int i = 0; i < time; i++)
|
||||
{
|
||||
totalDistance += velocity;
|
||||
velocity = ((velocity - GRAVITY) * MULTIPLIER);
|
||||
velocity = ((velocity - acceleration) * MULTIPLIER);
|
||||
velocity = Math.min(velocity, TERMINAL_VELOCITY);
|
||||
}
|
||||
|
||||
return totalDistance;
|
||||
}
|
||||
|
||||
public static int calculateFallTime(double initialVelocity, double distance) {
|
||||
public static int calculateFallTime(double initialVelocity, double distance, double acceleration) {
|
||||
double velocity = Math.abs(initialVelocity);
|
||||
int ticks = 0;
|
||||
|
||||
while (distance > 0) {
|
||||
velocity += GRAVITY;
|
||||
if (ticks > MAX_TICKS)
|
||||
return -1;
|
||||
|
||||
velocity += acceleration;
|
||||
velocity = Math.min(velocity, TERMINAL_VELOCITY);
|
||||
velocity *= MULTIPLIER;
|
||||
distance -= velocity;
|
||||
@@ -35,9 +38,9 @@ public class MathUtil {
|
||||
return ticks;
|
||||
}
|
||||
|
||||
public static int calculateTimeToMaxVelocity(double targetVerticalVelocity) {
|
||||
double a = -GRAVITY * MULTIPLIER;
|
||||
double b = GRAVITY + TERMINAL_VELOCITY * MULTIPLIER;
|
||||
public static int calculateTimeToMaxVelocity(double targetVerticalVelocity, double acceleration) {
|
||||
double a = -acceleration * MULTIPLIER;
|
||||
double b = acceleration + TERMINAL_VELOCITY * MULTIPLIER;
|
||||
double c = -2 * targetVerticalVelocity;
|
||||
|
||||
double discriminant = b * b - 4 * a * c;
|
||||
@@ -47,4 +50,11 @@ public class MathUtil {
|
||||
double positiveRoot = (-b + Math.sqrt(discriminant)) / (2 * a);
|
||||
return (int) Math.ceil(positiveRoot * 20);
|
||||
}
|
||||
|
||||
public static double clamp(double num, double min, double max) {
|
||||
if (num < min)
|
||||
return min;
|
||||
|
||||
return Math.min(num, max);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,11 @@ runnable:
|
||||
# The minimum change in ping required for it to be considered a lag spike.
|
||||
# If the difference between the latest and previous ping is greater than or equal to
|
||||
# the threshold, the previous ping value is used to avoid calculation inaccuracies.
|
||||
spike_threshold: 25
|
||||
spike_threshold: 20
|
||||
|
||||
enable_message: "&aSuccessfully enabled KnockbackSync."
|
||||
disable_message: "&cSuccessfully disabled KnockbackSync."
|
||||
disable_message: "&cSuccessfully disabled KnockbackSync."
|
||||
player_enable_message: "&aSuccessfully enabled KnockbackSync for %player%."
|
||||
player_disable_message: "&cSuccessfully disabled KnockbackSync for %player%."
|
||||
player_ineligible_message: "&c%player% is ineligible for KnockbackSync. If you believe this is an error, please open an issue on the github page."
|
||||
reload_message: "&aSuccessfully reloaded KnockbackSync config."
|
||||
@@ -1,5 +1,5 @@
|
||||
name: KnockbackSync
|
||||
version: '1.3'
|
||||
version: '1.3.2'
|
||||
main: me.caseload.knockbacksync.KnockbackSync
|
||||
api-version: '1.18'
|
||||
authors: [ Caseload ]
|
||||
@@ -7,7 +7,11 @@ description: Synchronizes player knockback for smoother gameplay.
|
||||
permissions:
|
||||
knockbacksync.update:
|
||||
default: op
|
||||
knockbacksync.toggle:
|
||||
knockbacksync.toggle.global:
|
||||
default: op
|
||||
knockbacksync.toggle.other:
|
||||
default: op
|
||||
knockbacksync.toggle.self:
|
||||
default: op
|
||||
knockbacksync.ping:
|
||||
default: op
|
||||
Reference in new issue
Block a user