This commit is contained in:
caseload authored and GitHub committed 2024-09-24 15:21:34 -05:00
1 parent e61fe99511
commit b3570e007b
17 files changed
+840

No files matched your search

+13
View File
@@ -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>
+102
View File
@@ -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);
}
}
+27
View File
@@ -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."
+13
View File
@@ -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