Add src
This commit is contained in:
1 parent
e61fe99511
commit
b3570e007b
17 files changed
+840
No files matched your search
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="minecraft" name="Minecraft">
|
||||
<configuration>
|
||||
<autoDetectTypes>
|
||||
<platformType>SPIGOT</platformType>
|
||||
</autoDetectTypes>
|
||||
<projectReimportVersion>1</projectReimportVersion>
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,102 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>me.caseload</groupId>
|
||||
<artifactId>KnockbackSync</artifactId>
|
||||
<version>1.3</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>KnockbackSync</name>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<defaultGoal>clean package</defaultGoal>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>dev.jorel.commandapi</pattern>
|
||||
<shadedPattern>me.caseload.knockbacksync.commandapi</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spigotmc-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>codemc-releases</id>
|
||||
<url>https://repo.codemc.io/repository/maven-releases/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.18.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.retrooper</groupId>
|
||||
<artifactId>packetevents-spigot</artifactId>
|
||||
<version>2.5.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.jorel</groupId>
|
||||
<artifactId>commandapi-bukkit-shade</artifactId>
|
||||
<version>9.5.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.34</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.kohsuke</groupId>
|
||||
<artifactId>github-api</artifactId>
|
||||
<version>1.326</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,108 @@
|
||||
package me.caseload.knockbacksync;
|
||||
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
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 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
|
||||
public final class KnockbackSync extends JavaPlugin {
|
||||
|
||||
private boolean toggled;
|
||||
private boolean runnable;
|
||||
private boolean updateAvailable;
|
||||
|
||||
private long runnableInterval;
|
||||
private long combatTimer;
|
||||
private long spikeThreshold;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
CommandAPI.onLoad(new CommandAPIBukkitConfig(this));
|
||||
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this));
|
||||
PacketEvents.getAPI().load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
checkForUpdates();
|
||||
saveDefaultConfig();
|
||||
|
||||
CommandAPI.onEnable();
|
||||
new MainCommand().register();
|
||||
|
||||
registerListeners(
|
||||
new PlayerDamageListener(),
|
||||
new PlayerKnockbackListener(),
|
||||
new PlayerJoinQuitListener()
|
||||
);
|
||||
|
||||
PacketEvents.getAPI().getEventManager().registerListener(
|
||||
new PingReceiveListener()
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
CommandAPI.onDisable();
|
||||
PacketEvents.getAPI().terminate();
|
||||
}
|
||||
|
||||
public static KnockbackSync getInstance() {
|
||||
return getPlugin(KnockbackSync.class);
|
||||
}
|
||||
|
||||
private void registerListeners(Listener... listeners) {
|
||||
PluginManager pluginManager = getServer().getPluginManager();
|
||||
for (Listener listener : listeners)
|
||||
pluginManager.registerEvents(listener, this);
|
||||
}
|
||||
|
||||
private void checkForUpdates() {
|
||||
getLogger().info("Checking for updates...");
|
||||
|
||||
Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
|
||||
try {
|
||||
GitHub github = GitHub.connectAnonymously();
|
||||
String latestVersion = github.getRepository("CASELOAD7000/knockback-sync")
|
||||
.getLatestRelease()
|
||||
.getTagName();
|
||||
|
||||
String currentVersion = getDescription().getVersion();
|
||||
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");
|
||||
} else {
|
||||
getLogger().info("You are running the latest release.");
|
||||
}
|
||||
|
||||
setUpdateAvailable(updateAvailable);
|
||||
} catch (Exception e) {
|
||||
getLogger().severe("Failed to check for updates: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package me.caseload.knockbacksync.command;
|
||||
|
||||
import dev.jorel.commandapi.CommandAPICommand;
|
||||
import me.caseload.knockbacksync.command.subcommand.PingSubcommand;
|
||||
import me.caseload.knockbacksync.command.subcommand.ToggleSubcommand;
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
public class MainCommand {
|
||||
|
||||
public void register() {
|
||||
new CommandAPICommand("knockbacksync")
|
||||
.withAliases("kbsync")
|
||||
.withSubcommand(new PingSubcommand().getCommand())
|
||||
.withSubcommand(new ToggleSubcommand().getCommand())
|
||||
.executes((sender, args) -> {
|
||||
sender.sendMessage(ChatColor.translateAlternateColorCodes(
|
||||
'&',
|
||||
"&6This server is running the &eKnockbackSync &6plugin. &bhttps://github.com/CASELOAD7000/knockback-sync"
|
||||
));
|
||||
})
|
||||
.register();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package me.caseload.knockbacksync.command.subcommand;
|
||||
|
||||
import dev.jorel.commandapi.CommandAPICommand;
|
||||
import dev.jorel.commandapi.arguments.PlayerArgument;
|
||||
import me.caseload.knockbacksync.manager.PlayerData;
|
||||
import me.caseload.knockbacksync.manager.PlayerDataManager;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class PingSubcommand {
|
||||
|
||||
public CommandAPICommand getCommand() {
|
||||
return new CommandAPICommand("ping")
|
||||
.withPermission("knockbacksync.ping")
|
||||
.withArguments(new PlayerArgument("target"))
|
||||
.executes((sender, args) -> {
|
||||
Player target = (Player) args.get("target");
|
||||
assert target != null;
|
||||
|
||||
PlayerData playerData = PlayerDataManager.getPlayerData(target.getUniqueId());
|
||||
sender.sendMessage(target.getName() + "'s last ping packet took " + playerData.getPing() + "ms.");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package me.caseload.knockbacksync.command.subcommand;
|
||||
|
||||
import dev.jorel.commandapi.CommandAPICommand;
|
||||
import me.caseload.knockbacksync.KnockbackSync;
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
public class ToggleSubcommand {
|
||||
|
||||
public CommandAPICommand getCommand() {
|
||||
return new CommandAPICommand("toggle")
|
||||
.withPermission("knockbacksync.toggle")
|
||||
.executes((sender, args) -> {
|
||||
boolean toggledState = !KnockbackSync.getInstance().isToggled();
|
||||
|
||||
KnockbackSync.getInstance().getConfig().set("enabled", toggledState);
|
||||
KnockbackSync.getInstance().saveConfig();
|
||||
KnockbackSync.getInstance().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.")
|
||||
);
|
||||
|
||||
sender.sendMessage(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package me.caseload.knockbacksync.listener;
|
||||
|
||||
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPong;
|
||||
import me.caseload.knockbacksync.KnockbackSync;
|
||||
import me.caseload.knockbacksync.manager.PlayerData;
|
||||
import me.caseload.knockbacksync.manager.PlayerDataManager;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class PingReceiveListener extends PacketListenerAbstract {
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (!KnockbackSync.getInstance().isToggled())
|
||||
return;
|
||||
|
||||
if (event.getPacketType() != PacketType.Play.Client.PONG)
|
||||
return;
|
||||
|
||||
Player player = event.getPlayer();
|
||||
PlayerData playerData = PlayerDataManager.getPlayerData(player.getUniqueId());
|
||||
|
||||
int packetId = new WrapperPlayClientPong(event).getId();
|
||||
|
||||
Long sendTime = playerData.getTimeline().get(packetId);
|
||||
if (sendTime == null)
|
||||
return;
|
||||
|
||||
long ping = System.currentTimeMillis() - sendTime;
|
||||
|
||||
playerData.getTimeline().remove(packetId);
|
||||
playerData.setPreviousPing(playerData.getPing() != null ? playerData.getPing() : ping);
|
||||
playerData.setPing(ping);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package me.caseload.knockbacksync.listener;
|
||||
|
||||
import me.caseload.knockbacksync.KnockbackSync;
|
||||
import me.caseload.knockbacksync.manager.PlayerData;
|
||||
import me.caseload.knockbacksync.manager.PlayerDataManager;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
|
||||
public class PlayerDamageListener implements Listener {
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerDamage(EntityDamageByEntityEvent event) {
|
||||
if (!KnockbackSync.getInstance().isToggled())
|
||||
return;
|
||||
|
||||
if (!(event.getEntity() instanceof Player victim) || !(event.getDamager() instanceof Player attacker))
|
||||
return;
|
||||
|
||||
PlayerData playerData = PlayerDataManager.getPlayerData(victim.getUniqueId());
|
||||
playerData.setVerticalVelocity(playerData.calculateVerticalVelocity(attacker)); // do not move this calculation
|
||||
playerData.updateCombat();
|
||||
|
||||
if (!KnockbackSync.getInstance().isRunnable())
|
||||
playerData.sendPing();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package me.caseload.knockbacksync.listener;
|
||||
|
||||
import me.caseload.knockbacksync.KnockbackSync;
|
||||
import me.caseload.knockbacksync.manager.PlayerData;
|
||||
import me.caseload.knockbacksync.manager.PlayerDataManager;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerKickEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerJoinQuitListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
PlayerDataManager.addPlayerData(player.getUniqueId(), new PlayerData(player));
|
||||
|
||||
if (KnockbackSync.getInstance().isUpdateAvailable() && 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"
|
||||
));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
UUID uuid = event.getPlayer().getUniqueId();
|
||||
PlayerData playerData = PlayerDataManager.getPlayerData(uuid);
|
||||
|
||||
if (playerData.isInCombat())
|
||||
playerData.quitCombat(true);
|
||||
|
||||
PlayerDataManager.removePlayerData(uuid);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerKick(PlayerKickEvent event) {
|
||||
UUID uuid = event.getPlayer().getUniqueId();
|
||||
PlayerData playerData = PlayerDataManager.getPlayerData(uuid);
|
||||
|
||||
if (playerData.isInCombat())
|
||||
playerData.quitCombat(true);
|
||||
|
||||
PlayerDataManager.removePlayerData(uuid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package me.caseload.knockbacksync.listener;
|
||||
|
||||
import me.caseload.knockbacksync.KnockbackSync;
|
||||
import me.caseload.knockbacksync.manager.PlayerData;
|
||||
import me.caseload.knockbacksync.manager.PlayerDataManager;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.player.PlayerVelocityEvent;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
public class PlayerKnockbackListener implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onPlayerVelocity(PlayerVelocityEvent event) {
|
||||
if (!KnockbackSync.getInstance().isToggled())
|
||||
return;
|
||||
|
||||
Player victim = event.getPlayer();
|
||||
PlayerData playerData = PlayerDataManager.getPlayerData(victim.getUniqueId());
|
||||
|
||||
EntityDamageEvent entityDamageEvent = victim.getLastDamageCause();
|
||||
if (entityDamageEvent == null)
|
||||
return;
|
||||
|
||||
EntityDamageEvent.DamageCause damageCause = entityDamageEvent.getCause();
|
||||
if (damageCause != EntityDamageEvent.DamageCause.ENTITY_ATTACK)
|
||||
return;
|
||||
|
||||
Entity attacker = ((EntityDamageByEntityEvent) entityDamageEvent).getDamager();
|
||||
if (!(attacker instanceof Player))
|
||||
return;
|
||||
|
||||
Vector velocity = victim.getVelocity();
|
||||
Double verticalVelocity = playerData.getVerticalVelocity();
|
||||
|
||||
if (verticalVelocity == null || !playerData.isOnGround(velocity.getY()))
|
||||
return;
|
||||
|
||||
Vector adjustedVelocity = velocity.clone().setY(
|
||||
verticalVelocity
|
||||
);
|
||||
|
||||
victim.setVelocity(adjustedVelocity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.caseload.knockbacksync.manager;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class CombatManager {
|
||||
|
||||
private static final Set<UUID> combatPlayers = new HashSet<>();
|
||||
|
||||
public static @NotNull Set<UUID> getPlayers() {
|
||||
return Collections.unmodifiableSet(combatPlayers);
|
||||
}
|
||||
|
||||
public static void addPlayer(UUID uuid) {
|
||||
combatPlayers.add(uuid);
|
||||
}
|
||||
|
||||
public static void removePlayer(UUID uuid) {
|
||||
combatPlayers.remove(uuid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package me.caseload.knockbacksync.manager;
|
||||
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPing;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import me.caseload.knockbacksync.KnockbackSync;
|
||||
import me.caseload.knockbacksync.util.MathUtil;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.bukkit.util.RayTraceResult;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
@Getter
|
||||
public class PlayerData {
|
||||
|
||||
private final Player player;
|
||||
|
||||
// Please read the GitHub FAQ before adjusting.
|
||||
private static final long PING_OFFSET = 20;
|
||||
|
||||
@NotNull
|
||||
private final Map<Integer, Long> timeline = new HashMap<>();
|
||||
|
||||
@NotNull
|
||||
private final Random random = new Random();
|
||||
|
||||
@Nullable
|
||||
private BukkitTask combatTask;
|
||||
|
||||
@Nullable @Setter
|
||||
private Long ping, previousPing;
|
||||
|
||||
@Nullable @Setter
|
||||
private Double verticalVelocity;
|
||||
|
||||
public PlayerData(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the player's ping with compensation for lag spikes.
|
||||
* A hardcoded offset is applied for several reasons,
|
||||
* read the GitHub FAQ before adjusting.
|
||||
*
|
||||
* @return The compensated ping, with a minimum of 1.
|
||||
*/
|
||||
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;
|
||||
|
||||
return Math.max(1, ping - PING_OFFSET);
|
||||
}
|
||||
|
||||
public void sendPing() {
|
||||
int packetId = random.nextInt(1, 10000);
|
||||
|
||||
timeline.put(packetId, System.currentTimeMillis());
|
||||
|
||||
WrapperPlayServerPing packet = new WrapperPlayServerPing(packetId);
|
||||
PacketEvents.getAPI().getPlayerManager().sendPacket(player, packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the Player is on the ground, either serverside or clientside.
|
||||
* <p>
|
||||
* Returns <code>true</code> if:
|
||||
* <ul>
|
||||
* <li>Clientside: <code>ping ≥ (tMax + tFall)</code> and <code>gDist ≤ 1.3</code></li>
|
||||
* <li>Serverside: <code>gDist ≤ 0</code></li>
|
||||
* </ul>
|
||||
*
|
||||
* Where:
|
||||
* <ul>
|
||||
* <li><code>ping</code>: Estimated latency</li>
|
||||
* <li><code>tMax</code>: Time to reach maximum upward velocity</li>
|
||||
* <li><code>tFall</code>: Time to fall to the ground</li>
|
||||
* <li><code>gDist</code>: Distance to the ground</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param verticalVelocity The Player's current vertical velocity.
|
||||
* @return <code>true</code> if the Player is on the ground; <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean isOnGround(double verticalVelocity) {
|
||||
Material material = player.getLocation().getBlock().getType();
|
||||
if (player.isGliding() || material == Material.WATER || material == Material.LAVA
|
||||
|| material == Material.COBWEB || material == Material.SCAFFOLDING)
|
||||
return false;
|
||||
|
||||
if (ping == null || ping < PING_OFFSET)
|
||||
return false;
|
||||
|
||||
double gDist = getDistanceToGround();
|
||||
if (gDist <= 0)
|
||||
return true;
|
||||
|
||||
int tMax = verticalVelocity > 0 ? MathUtil.calculateTimeToMaxVelocity(verticalVelocity) : 0;
|
||||
double mH = verticalVelocity > 0 ? MathUtil.calculateDistanceTraveled(verticalVelocity, tMax) : 0;
|
||||
int tFall = MathUtil.calculateFallTime(verticalVelocity, mH + gDist);
|
||||
|
||||
return getEstimatedPing() >= tMax + tFall / 20.0 * 1000 && gDist <= 1.3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ray traces from each corner of the player's bounding box to the ground,
|
||||
* returning the smallest distance, with a maximum limit of 5 blocks.
|
||||
*
|
||||
* @return The distance to the ground in blocks
|
||||
*/
|
||||
public double getDistanceToGround() {
|
||||
double collisionDist = 5;
|
||||
|
||||
World world = player.getWorld();
|
||||
|
||||
for (Location corner : getBBCorners()) {
|
||||
RayTraceResult result = world.rayTraceBlocks(corner, new Vector(0, -1, 0), 5, FluidCollisionMode.NEVER, true);
|
||||
if (result == null || result.getHitBlock() == null)
|
||||
continue;
|
||||
|
||||
collisionDist = Math.min(collisionDist, corner.getY() - result.getHitBlock().getY());
|
||||
}
|
||||
|
||||
return collisionDist - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the corners of the Player's bounding box.
|
||||
*
|
||||
* @return An array of locations representing the corners of the bounding box.
|
||||
*/
|
||||
public Location @NotNull [] getBBCorners() {
|
||||
BoundingBox boundingBox = player.getBoundingBox();
|
||||
Location location = player.getLocation();
|
||||
World world = location.getWorld();
|
||||
|
||||
double adjustment = 0.01; // To ensure the bounding box isn't clipping inside a wall
|
||||
|
||||
return new Location[] {
|
||||
new Location(world, boundingBox.getMinX() + adjustment, location.getY(), boundingBox.getMinZ() + adjustment),
|
||||
new Location(world, boundingBox.getMinX() + adjustment, location.getY(), boundingBox.getMaxZ() - adjustment),
|
||||
new Location(world, boundingBox.getMaxX() - adjustment, location.getY(), boundingBox.getMinZ() + adjustment),
|
||||
new Location(world, boundingBox.getMaxX() - adjustment, location.getY(), boundingBox.getMaxZ() - adjustment)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the positive vertical velocity.
|
||||
* This is used to switch falling knockback to rising knockback.
|
||||
*
|
||||
* @param attacker The player who is attacking.
|
||||
* @return The calculated positive vertical velocity, consistent with vanilla behavior.
|
||||
*/
|
||||
public double calculateVerticalVelocity(Player attacker) {
|
||||
double yAxis = attacker.getAttackCooldown() > 0.848 ? 0.4 : 0.36080000519752503;
|
||||
|
||||
if (!attacker.isSprinting()) {
|
||||
yAxis = 0.36080000519752503;
|
||||
double knockbackResistance = player.getAttribute(Attribute.GENERIC_KNOCKBACK_RESISTANCE).getValue();
|
||||
double resistanceFactor = 0.04000000119 * knockbackResistance * 10;
|
||||
yAxis -= resistanceFactor;
|
||||
}
|
||||
|
||||
return yAxis;
|
||||
}
|
||||
|
||||
// might need soon
|
||||
public double calculateJumpVelocity() {
|
||||
double jumpVelocity = 0.42;
|
||||
|
||||
PotionEffect jumpEffect = player.getPotionEffect(PotionEffectType.JUMP);
|
||||
if (jumpEffect != null) {
|
||||
int amplifier = jumpEffect.getAmplifier();
|
||||
jumpVelocity += (amplifier + 1) * 0.1F;
|
||||
}
|
||||
|
||||
return jumpVelocity;
|
||||
}
|
||||
|
||||
public boolean isInCombat() {
|
||||
return combatTask != null;
|
||||
}
|
||||
|
||||
public void updateCombat() {
|
||||
if (isInCombat())
|
||||
combatTask.cancel();
|
||||
|
||||
combatTask = newCombatTask();
|
||||
CombatManager.addPlayer(player.getUniqueId());
|
||||
}
|
||||
|
||||
public void quitCombat(boolean cancelTask) {
|
||||
if (cancelTask)
|
||||
combatTask.cancel();
|
||||
|
||||
combatTask = null;
|
||||
CombatManager.removePlayer(player.getUniqueId());
|
||||
timeline.clear(); // failsafe for packet loss idk
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private BukkitTask newCombatTask() {
|
||||
return Bukkit.getScheduler().runTaskLaterAsynchronously(KnockbackSync.getInstance(),
|
||||
() -> quitCombat(false), KnockbackSync.getInstance().getCombatTimer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package me.caseload.knockbacksync.manager;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class PlayerDataManager {
|
||||
|
||||
private static final Map<UUID, PlayerData> playerDataMap = new HashMap<>();
|
||||
|
||||
public static @NotNull PlayerData getPlayerData(UUID uuid) {
|
||||
return playerDataMap.get(uuid);
|
||||
}
|
||||
|
||||
public static void addPlayerData(UUID uuid, PlayerData playerData) {
|
||||
playerDataMap.put(uuid, playerData);
|
||||
}
|
||||
|
||||
public static void removePlayerData(UUID uuid) {
|
||||
playerDataMap.remove(uuid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package me.caseload.knockbacksync.runnable;
|
||||
|
||||
import me.caseload.knockbacksync.KnockbackSync;
|
||||
import me.caseload.knockbacksync.manager.CombatManager;
|
||||
import me.caseload.knockbacksync.manager.PlayerData;
|
||||
import me.caseload.knockbacksync.manager.PlayerDataManager;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PingRunnable extends BukkitRunnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!KnockbackSync.getInstance().isToggled())
|
||||
return;
|
||||
|
||||
for (UUID uuid : CombatManager.getPlayers()) {
|
||||
PlayerData playerData = PlayerDataManager.getPlayerData(uuid);
|
||||
playerData.sendPing();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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;
|
||||
|
||||
public static double calculateDistanceTraveled(double velocity, int time)
|
||||
{
|
||||
double totalDistance = 0;
|
||||
|
||||
for (int i = 0; i < time; i++)
|
||||
{
|
||||
totalDistance += velocity;
|
||||
velocity = ((velocity - GRAVITY) * MULTIPLIER);
|
||||
velocity = Math.min(velocity, TERMINAL_VELOCITY);
|
||||
}
|
||||
|
||||
return totalDistance;
|
||||
}
|
||||
|
||||
public static int calculateFallTime(double initialVelocity, double distance) {
|
||||
double velocity = Math.abs(initialVelocity);
|
||||
int ticks = 0;
|
||||
|
||||
while (distance > 0) {
|
||||
velocity += GRAVITY;
|
||||
velocity = Math.min(velocity, TERMINAL_VELOCITY);
|
||||
velocity *= MULTIPLIER;
|
||||
distance -= velocity;
|
||||
ticks++;
|
||||
}
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
public static int calculateTimeToMaxVelocity(double targetVerticalVelocity) {
|
||||
double a = -GRAVITY * MULTIPLIER;
|
||||
double b = GRAVITY + TERMINAL_VELOCITY * MULTIPLIER;
|
||||
double c = -2 * targetVerticalVelocity;
|
||||
|
||||
double discriminant = b * b - 4 * a * c;
|
||||
if (discriminant < 0)
|
||||
return 0;
|
||||
|
||||
double positiveRoot = (-b + Math.sqrt(discriminant)) / (2 * a);
|
||||
return (int) Math.ceil(positiveRoot * 20);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#########################################
|
||||
# KnockbackSync Configuration #
|
||||
#########################################
|
||||
|
||||
# Plugin enabled state
|
||||
# Toggleable using /knockbacksync toggle
|
||||
# Required permission: knockbacksync.toggle
|
||||
enabled: true
|
||||
|
||||
# Notify staff about update availability
|
||||
# Required permission: knockbacksync.update
|
||||
notify_updates: true
|
||||
|
||||
# Grabs the ping of combat-tagged players every x ticks
|
||||
# Disabling this may lead to inaccuracies in calculations
|
||||
runnable:
|
||||
enabled: true # Runnable enabled state
|
||||
interval: 5 # The interval in ticks between sending out pings to players
|
||||
combat_timer: 30 # The timer in ticks before being considered out of combat
|
||||
|
||||
# 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
|
||||
|
||||
enable_message: "&aSuccessfully enabled KnockbackSync."
|
||||
disable_message: "&cSuccessfully disabled KnockbackSync."
|
||||
@@ -0,0 +1,13 @@
|
||||
name: KnockbackSync
|
||||
version: '1.3'
|
||||
main: me.caseload.knockbacksync.KnockbackSync
|
||||
api-version: '1.18'
|
||||
authors: [ Caseload ]
|
||||
description: Synchronizes player knockback for smoother gameplay.
|
||||
permissions:
|
||||
knockbacksync.update:
|
||||
default: op
|
||||
knockbacksync.toggle:
|
||||
default: op
|
||||
knockbacksync.ping:
|
||||
default: op
|
||||
Reference in new issue
Block a user