4 Commits
Author SHA1 Message Date
Axionize 9b14df0881 Add fabric build to alpha channel 2024-10-18 15:54:37 -04:00
Axionize 11b2c3ff41 Revert Build-gradle hashing to use Axionize repo 2024-10-18 15:53:01 -04:00
Axionize b842426448 Fix not loading on 1.16.5 2024-10-18 15:39:58 -04:00
Axionize 3f789ecd25 Downgrade to Java 8 2024-10-18 15:32:07 -04:00
65 changed files with 1090 additions and 1283 deletions

No files matched your search

+23 -42
View File
@@ -115,53 +115,35 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Download the current dev-builds.txt
curl -L -o dev-builds.txt https://github.com/CASELOAD7000/knockback-sync/releases/latest/download/dev-builds.txt
curl -L -o dev-builds.txt https://github.com/Axionize/knockback-sync/releases/download/hashlist/dev-builds.txt
# Create a copy of the original file
cp dev-builds.txt dev-builds.txt.original
# Ensure the file ends with a newline
# Ensure the file ends with a newline, then append new information
sed -i -e '$a\' dev-builds.txt
# echo "KnockBackSync-${{ env.NEW_VERSION }}.jar" >> dev-builds.txt
echo "${{ env.ORIGINAL_HASH }}" >> dev-builds.txt
# echo "KnockBackSync-${{ env.NEW_VERSION }}-remapped.jar" >> dev-builds.txt
echo "${{ env.REMAPPED_HASH }}" >> dev-builds.txt
# Function to add hash if it doesn't exist
add_hash_if_new() {
if ! grep -q "$1" dev-builds.txt; then
echo "$1" >> dev-builds.txt
fi
}
echo "${{ env.FABRIC_HASH }}" >> dev-builds.txt
# Add new hashes if they don't exist
add_hash_if_new "${{ env.ORIGINAL_HASH }}"
add_hash_if_new "${{ env.REMAPPED_HASH }}"
add_hash_if_new "${{ env.FABRIC_HASH }}"
# Get the release ID for the 'hashlist' release
RELEASE_ID=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/Axionize/knockback-sync/releases/tags/hashlist" | jq -r .id)
# Check if the file has changed
if cmp -s dev-builds.txt dev-builds.txt.original; then
echo "No changes to dev-builds.txt, skipping upload."
else
echo "Changes detected in dev-builds.txt, uploading new version."
# Get the latest release ID
RELEASE_ID=$(curl -s "https://api.github.com/repos/CASELOAD7000/knockback-sync/releases/latest" | jq -r .id)
# Delete the old dev-builds.txt asset
ASSET_ID=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/CASELOAD7000/knockback-sync/releases/$RELEASE_ID/assets" | \
jq -r '.[] | select(.name == "dev-builds.txt") | .id')
if [ ! -z "$ASSET_ID" ]; then
curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/CASELOAD7000/knockback-sync/releases/assets/$ASSET_ID"
fi
# Upload the new dev-builds.txt
curl -X POST -H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @dev-builds.txt \
"https://uploads.github.com/repos/CASELOAD7000/knockback-sync/releases/$RELEASE_ID/assets?name=dev-builds.txt"
# Delete the old dev-builds.txt asset
ASSET_ID=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/Axionize/knockback-sync/releases/$RELEASE_ID/assets" | \
jq -r '.[] | select(.name == "dev-builds.txt") | .id')
if [ ! -z "$ASSET_ID" ]; then
curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/Axionize/knockback-sync/releases/assets/$ASSET_ID"
fi
# Clean up
rm dev-builds.txt.original
# Upload the new dev-builds.txt
curl -X POST -H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @dev-builds.txt \
"https://uploads.github.com/repos/Axionize/knockback-sync/releases/$RELEASE_ID/assets?name=dev-builds.txt"
- name: Create artifacts directory
run: mkdir -p artifacts
@@ -201,7 +183,6 @@ jobs:
spigot
paper
folia
purpur
game-versions: |
>=1.16.5
@@ -222,7 +203,7 @@ jobs:
name: KnockbackSync-${{ env.NEW_VERSION }}
version: ${{ env.NEW_VERSION }}
version-type: beta
version-type: alpha
loaders: |
fabric
+1 -10
View File
@@ -18,7 +18,6 @@ Official Discord: https://discord.gg/nnpqpAtyVW
### How does this change benefit high ping players?
**Knockback control.** For example, it will be easier to escape crit chains and punish crit.
<<<<<<< Updated upstream
### How do I change the ping offset?
Open up `config.yml` and edit the line that says `ping_offset: 25`. Replace 25 with your desired ping offset.
;
@@ -27,14 +26,6 @@ Then just type
/knockbacksync reload
```
or restart your server.
=======
### 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 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](common/src/main/java/me/caseload/knockbacksync/player/PlayerData.java) class.
>>>>>>> Stashed changes
## What servers are using this plugin?
| IP | Location | Region | Ping Offset | spike_threshold |
@@ -178,4 +169,4 @@ This command reloads the KnockbackSync plugin's configuration file.
## License
GNU General Public License v3.0 or later
See [LICENSE](https://www.gnu.org/licenses/gpl-3.0.en.html) to see the full text.
See [LICENSE](LICENSE) to see the full text.
+17
View File
@@ -52,4 +52,21 @@ subprojects {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
// if (project.name != "common") {
// tasks.withType<JavaCompile> {
// source(project(":common").sourceSets.main.get().allSource)
// }
// tasks.withType<Javadoc> {
// source(project(":common").sourceSets.main.get().allJava)
// }
// tasks.named<Jar>("sourcesJar") {
// from(project(":common").sourceSets.main.get().allSource)
// }
// tasks.named<JavaCompile>("compileTestJava") {
// exclude("**/*")
// }
// }
}
+59 -5
View File
@@ -2,8 +2,12 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
id("com.gradleup.shadow")
// id("io.papermc.paperweight.userdev") version "1.7.3"
// id("net.neoforged.moddev") version "1.0.11"
}
//paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.REOBF_PRODUCTION
base {
archivesName.set("${rootProject.property("archives_base_name")}-bukkit")
}
@@ -13,9 +17,23 @@ val shadeThisThing: Configuration by configurations.creating {
isTransitive = true
}
// TODO migrate to only including sources sets for compile, test and javadoc tasks
//sourceSets {
// main {
// java {
// srcDir(project(":common").sourceSets.main.get().java.srcDirs)
// }
// resources {
// srcDir(project(":common").sourceSets.main.get().resources.srcDirs)
// }
// }
//}
// TODO migrate to only including sourceset for compile, test and javadoc tasks
// Currently must build with gradle build -x test to skip test
tasks.withType<JavaCompile>().configureEach {
source(project(":common").sourceSets.main.get().allSource)
source(project(":common").sourceSets.main.get().java.srcDirs)
source(project(":common").sourceSets.main.get().resources.srcDirs())
options.annotationProcessorPath = configurations["annotationProcessor"] + configurations["compileClasspath"]
}
tasks.withType<Javadoc>().configureEach {
@@ -26,34 +44,60 @@ tasks.named<JavaCompile>("compileTestJava") {
exclude("**/*")
}
// Dirty hack exists so the build process will finish running
//tasks.withType<Test>().configureEach {
// classpath += project(":common").sourceSets["main"].output + configurations["compileClasspath"] + configurations["runtimeClasspath"]
//}
dependencies {
// shadeThisThing(implementation(project(":common"))!!)
implementation(project(":common"))
// paperweight.paperDevBundle("1.16.5-R0.1-SNAPSHOT")
// compileOnly("com.mojang:brigadier:1.0.18")
compileOnly("org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT")
compileOnly("org.geysermc.floodgate:api:2.0-SNAPSHOT")
// compileOnly("dev.folia:folia-api:1.20.4-R0.1-SNAPSHOT")
compileOnly("org.projectlombok:lombok:1.18.34")
annotationProcessor("org.projectlombok:lombok:1.18.34")
shadeThisThing(implementation("org.kohsuke:github-api:1.326")!!)
// shadeThisThing(implementation("org.kohsuke:github-api:1.326") {
// exclude(group = "commons-io", module = "commons-io")
// exclude(group = "org.apache.commons", module = "commons-lang3")
// })
shadeThisThing(implementation("com.fasterxml.jackson.core:jackson-databind:2.17.2")!!)
shadeThisThing(implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.2")!!)
shadeThisThing(implementation("com.github.retrooper:packetevents-spigot:2.5.0")!!)
// shadeThisThing(implementation("net.kyori:adventure-platform-bukkit:4.3.4")!!)
shadeThisThing(implementation("org.incendo:cloud-paper:2.0.0-beta.10")!!)
shadeThisThing(implementation("org.incendo:cloud-core:2.0.0")!!)
shadeThisThing(implementation("dev.jorel:commandapi-bukkit-shade:9.5.3")!!)
}
tasks.withType<ShadowJar> {
// Exclude Java 21 specific classes
// exclude("META-INF/**")
// archiveClassifier.set("-all")
configurations = listOf(shadeThisThing)
isEnableRelocation = true
relocationPrefix = "${project.property("maven_group")}.${project.property("archives_base_name")}.shaded"
}
//tasks.named("reobfJar") {
// dependsOn(tasks.named("shadowJar"))
//}
tasks.build {
// dependsOn(tasks.named("reobfJar"))
dependsOn(tasks.shadowJar)
}
tasks.processResources {
from(project(":common").sourceSets.main.get().resources)
inputs.property("version", project.version)
@@ -64,4 +108,14 @@ tasks.processResources {
"version" to project.version,
)
}
}
//neoForge {
// Look for versions on https://projects.neoforged.net/neoforged/neoform
// neoFormVersion.set("1.21-20240613.152323")
//}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
@@ -1,138 +0,0 @@
package me.caseload.knockbacksync;
import com.github.retrooper.packetevents.PacketEvents;
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
import me.caseload.knockbacksync.listener.bukkit.BukkitPlayerDamageListener;
import me.caseload.knockbacksync.listener.bukkit.BukkitPlayerJoinQuitListener;
import me.caseload.knockbacksync.listener.bukkit.BukkitPlayerKnockbackListener;
import me.caseload.knockbacksync.manager.ConfigManager;
import me.caseload.knockbacksync.permission.PermissionChecker;
import me.caseload.knockbacksync.permission.PluginPermissionChecker;
import me.caseload.knockbacksync.scheduler.BukkitSchedulerAdapter;
import me.caseload.knockbacksync.scheduler.FoliaSchedulerAdapter;
import me.caseload.knockbacksync.sender.BukkitPlayerSelectorParser;
import me.caseload.knockbacksync.sender.BukkitSenderFactory;
import me.caseload.knockbacksync.sender.Sender;
import me.caseload.knockbacksync.stats.custom.BukkitStatsManager;
import me.caseload.knockbacksync.stats.custom.PluginJarHashProvider;
import me.caseload.knockbacksync.world.BukkitServer;
import org.bukkit.event.Listener;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.incendo.cloud.bukkit.CloudBukkitCapabilities;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.paper.LegacyPaperCommandManager;
import java.io.File;
import java.io.InputStream;
import java.util.logging.Logger;
public class KBSyncBukkitBase extends KnockbackSyncBase {
private final JavaPlugin plugin;
private final BukkitSenderFactory bukkitSenderFactory = new BukkitSenderFactory(this);
private final PluginPermissionChecker permissionChecker = new PluginPermissionChecker();
public KBSyncBukkitBase(JavaPlugin plugin) {
this.plugin = plugin;
super.configManager = new ConfigManager();
super.playerSelectorParser = new BukkitPlayerSelectorParser<>();
super.statsManager = new BukkitStatsManager();
super.platformServer = new BukkitServer();
super.pluginJarHashProvider = new PluginJarHashProvider(this.getClass().getProtectionDomain().getCodeSource().getLocation());
}
@Override
public Logger getLogger() {
return this.plugin.getLogger();
}
@Override
public File getDataFolder() {
return this.plugin.getDataFolder();
}
@Override
public InputStream getResource(String filename) {
return this.plugin.getResource(filename);
}
@Override
public void load() {
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this.plugin));
PacketEvents.getAPI().load();
}
@Override
public void enable() {
super.enable();
initializeScheduler();
configManager.loadConfig(false);
statsManager.init();
checkForUpdates();
}
@Override
public void initializeScheduler() {
switch (platform) {
case BUKKIT:
super.scheduler = new BukkitSchedulerAdapter(this.plugin);
break;
case FOLIA:
super.scheduler = new FoliaSchedulerAdapter(this.plugin);
break;
}
}
@Override
protected void registerPlatformListeners() {
registerPluginListeners(
new BukkitPlayerDamageListener(),
new BukkitPlayerKnockbackListener(),
new BukkitPlayerJoinQuitListener()
);
}
@Override
protected void registerCommands() {
super.commandManager = new LegacyPaperCommandManager<>(
this.plugin,
ExecutionCoordinator.simpleCoordinator(),
bukkitSenderFactory
);
if (commandManager instanceof LegacyPaperCommandManager) {
LegacyPaperCommandManager<Sender> legacyPaperCommandManager = (LegacyPaperCommandManager<Sender>) commandManager;
if (legacyPaperCommandManager.hasCapability(CloudBukkitCapabilities.NATIVE_BRIGADIER)) {
legacyPaperCommandManager.registerBrigadier();
} else if (commandManager.hasCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) {
legacyPaperCommandManager.registerAsynchronousCompletions();
}
}
super.registerCommands();
}
@Override
protected String getVersion() {
return this.plugin.getDescription().getVersion();
}
@Override
public void saveDefaultConfig() {
this.plugin.saveDefaultConfig();
}
@Override
public PermissionChecker getPermissionChecker() {
return permissionChecker;
}
private void registerPluginListeners(Listener... listeners) {
PluginManager pluginManager = this.plugin.getServer().getPluginManager();
for (Listener listener : listeners)
pluginManager.registerEvents(listener, this.plugin);
}
public BukkitSenderFactory getSenderFactory() {
return this.bukkitSenderFactory;
}
}
@@ -1,24 +0,0 @@
package me.caseload.knockbacksync;
import com.github.retrooper.packetevents.PacketEvents;
import org.bukkit.plugin.java.JavaPlugin;
public final class KBSyncBukkitLoaderPlugin extends JavaPlugin {
private final KnockbackSyncBase core = new KBSyncBukkitBase(this);
@Override
public void onLoad() {
core.load();
}
@Override
public void onEnable() {
core.enable();
}
@Override
public void onDisable() {
PacketEvents.getAPI().terminate();
}
}
@@ -0,0 +1,139 @@
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 me.caseload.knockbacksync.command.bukkit.MainCommand;
import me.caseload.knockbacksync.listener.bukkit.BukkitPlayerDamageListener;
import me.caseload.knockbacksync.listener.bukkit.BukkitPlayerJoinQuitListener;
import me.caseload.knockbacksync.listener.bukkit.BukkitPlayerKnockbackListener;
import me.caseload.knockbacksync.permission.PermissionChecker;
import me.caseload.knockbacksync.permission.PluginPermissionChecker;
import me.caseload.knockbacksync.scheduler.BukkitSchedulerAdapter;
import me.caseload.knockbacksync.scheduler.FoliaSchedulerAdapter;
import me.caseload.knockbacksync.stats.custom.BukkitStatsManager;
import me.caseload.knockbacksync.stats.custom.PluginJarHashProvider;
import me.caseload.knockbacksync.world.BukkitServer;
import org.bukkit.event.Listener;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.InputStream;
import java.util.logging.Logger;
public final class KnockbackSyncPlugin extends JavaPlugin {
private final KnockbackSyncBase core = new KnockbackSyncBase() {
private final PluginPermissionChecker permissionChecker = new PluginPermissionChecker();
{
statsManager = new BukkitStatsManager();
platformServer = new BukkitServer();
pluginJarHashProvider = new PluginJarHashProvider(this.getClass().getProtectionDomain().getCodeSource().getLocation());
}
@Override
public Logger getLogger() {
return KnockbackSyncPlugin.this.getLogger();
}
@Override
public File getDataFolder() {
return KnockbackSyncPlugin.this.getDataFolder();
}
@Override
public InputStream getResource(String filename) {
return KnockbackSyncPlugin.this.getResource(filename);
}
@Override
public void load() {
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(KnockbackSyncPlugin.this));
PacketEvents.getAPI().load();
}
@Override
public void enable() {
super.enable();
initializeScheduler();
configManager.loadConfig(false);
statsManager.init();
checkForUpdates();
}
@Override
public void initializeScheduler() {
switch (platform) {
case BUKKIT:
super.scheduler = new BukkitSchedulerAdapter(KnockbackSyncPlugin.this);
break;
case FOLIA:
super.scheduler = new FoliaSchedulerAdapter(KnockbackSyncPlugin.this);
break;
}
}
@Override
protected void registerPlatformListeners() {
registerPluginListeners(
new BukkitPlayerDamageListener(),
new BukkitPlayerKnockbackListener(),
new BukkitPlayerJoinQuitListener()
);
}
@Override
protected void registerCommands() {
new MainCommand().register();
// CommandDispatcher dispatcher = Brigadier.getCommandDispatcher();
// dispatcher.register(KnockbackSyncCommand.build());
// dispatcher.register(
// Commands.literal("kbsync")
// .redirect(dispatcher.getRoot().getChild("knockbacksync"))
// );
}
@Override
protected String getVersion() {
return getDescription().getVersion();
}
@Override
public void saveDefaultConfig() {
KnockbackSyncPlugin.this.saveDefaultConfig();
}
@Override
public PermissionChecker getPermissionChecker() {
return permissionChecker;
}
};
@Override
public void onLoad() {
CommandAPI.onLoad(new CommandAPIBukkitConfig(this));
core.load();
}
@Override
public void onEnable() {
CommandAPI.onEnable();
core.enable();
}
@Override
public void onDisable() {
CommandAPI.onDisable();
PacketEvents.getAPI().terminate();
}
private void registerPluginListeners(Listener... listeners) {
PluginManager pluginManager = getServer().getPluginManager();
for (Listener listener : listeners)
pluginManager.registerEvents(listener, this);
}
}
@@ -0,0 +1,27 @@
package me.caseload.knockbacksync.command.bukkit;
import dev.jorel.commandapi.CommandAPICommand;
import me.caseload.knockbacksync.command.bukkit.subcommand.PingSubcommand;
import me.caseload.knockbacksync.command.bukkit.subcommand.ReloadSubcommand;
import me.caseload.knockbacksync.command.bukkit.subcommand.StatusSubcommand;
import me.caseload.knockbacksync.command.bukkit.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())
.withSubcommand(new ReloadSubcommand().getCommand())
.withSubcommand(new StatusSubcommand().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,37 @@
package me.caseload.knockbacksync.command.bukkit.subcommand;
import dev.jorel.commandapi.CommandAPICommand;
import dev.jorel.commandapi.arguments.PlayerArgument;
import me.caseload.knockbacksync.command.PlatformSender;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.player.PlayerData;
import me.caseload.knockbacksync.util.ChatUtil;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
public class PingSubcommand {
public CommandAPICommand getCommand() {
return new CommandAPICommand("ping")
.withPermission("knockbacksync.ping")
.withOptionalArguments(new PlayerArgument("target"))
.executes((sender, args) -> {
Player target = (Player) args.get("target");
if (target == null) {
if (sender instanceof ConsoleCommandSender) {
sender.sendMessage(ChatColor.RED + "You must specify a player to use this command from the console.");
} else if (sender instanceof Player) {
sender.sendMessage(ChatUtil.getPingMessage(((Player) sender).getUniqueId(), null));
}
} else {
if (sender instanceof ConsoleCommandSender) {
sender.sendMessage(ChatUtil.getPingMessage(PlatformSender.CONSOLE_UUID, target.getUniqueId()));
} else if (sender instanceof Player) {
sender.sendMessage(ChatUtil.getPingMessage(((Player) sender).getUniqueId(), target.getUniqueId()));
}
}
});
}
}
@@ -0,0 +1,25 @@
package me.caseload.knockbacksync.command.bukkit.subcommand;
import dev.jorel.commandapi.CommandAPICommand;
import me.caseload.knockbacksync.KnockbackSyncBase;
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 = KnockbackSyncBase.INSTANCE.getConfigManager();
configManager.loadConfig(true);
String reloadMessage = ChatColor.translateAlternateColorCodes('&',
configManager.getReloadMessage()
);
sender.sendMessage(reloadMessage);
});
}
}
@@ -0,0 +1,58 @@
package me.caseload.knockbacksync.command.bukkit.subcommand;
import dev.jorel.commandapi.CommandAPICommand;
import dev.jorel.commandapi.CommandPermission;
import dev.jorel.commandapi.arguments.PlayerArgument;
import me.caseload.knockbacksync.KnockbackSyncBase;
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 = KnockbackSyncBase.INSTANCE.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") + ".");
}
});
}
}
@@ -0,0 +1,87 @@
package me.caseload.knockbacksync.command.bukkit.subcommand;
import dev.jorel.commandapi.CommandAPICommand;
import dev.jorel.commandapi.CommandPermission;
import dev.jorel.commandapi.arguments.PlayerArgument;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.manager.ConfigManager;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.player.BukkitPlayer;
import me.caseload.knockbacksync.player.PlayerData;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import java.util.UUID;
public class ToggleSubcommand implements Listener {
public CommandAPICommand getCommand() {
return new CommandAPICommand("toggle")
.withPermission(CommandPermission.NONE)
.withOptionalArguments(new PlayerArgument("target"))
.executes((sender, args) -> {
ConfigManager configManager = KnockbackSyncBase.INSTANCE.getConfigManager();
Player target = (Player) args.get("target");
String message;
if (target == null) {
// Global toggle
if (sender.hasPermission("knockbacksync.toggle.global")) {
boolean toggledState = !configManager.isToggled();
configManager.setToggled(toggledState);
KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper().set("enabled", toggledState);
KnockbackSyncBase.INSTANCE.getConfigManager().saveConfig();
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(new BukkitPlayer(target)));
String message = ChatColor.translateAlternateColorCodes('&',
hasPlayerData ? configManager.getPlayerDisableMessage() : configManager.getPlayerEnableMessage()
).replace("%player%", target.getName());
sender.sendMessage(message);
}
}
@@ -1,10 +1,8 @@
package me.caseload.knockbacksync.permission;
import me.caseload.knockbacksync.command.PlatformSender;
import me.caseload.knockbacksync.player.BukkitPlayer;
import me.caseload.knockbacksync.player.PlatformPlayer;
import me.caseload.knockbacksync.sender.Sender;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class PluginPermissionChecker implements PermissionChecker {
@@ -20,16 +18,13 @@ public class PluginPermissionChecker implements PermissionChecker {
// Currently shouldn't be called
@Override
public boolean hasPermission(Object nativeType, String s, boolean defaultIfUnset) {
if (nativeType instanceof CommandSender) {
return ((CommandSender) nativeType).hasPermission(s);
}
throw new IllegalArgumentException("Attempted to check permission of an object that wasn't a platformplayer or Sender. This should never happen on Bukkit!");
public boolean hasPermission(Object CommandSourceStack, String s, boolean defaultIfUnset) {
return false;
}
@Override
public boolean hasPermission(Sender source, String s, boolean defaultIfUnset) {
return source.hasPermission(s, defaultIfUnset);
public boolean hasPermission(PlatformSender source, String s, boolean defaultIfUnset) {
return false;
}
@Override
@@ -56,15 +56,9 @@ public class BukkitPlayer implements PlatformPlayer {
@Override
public boolean isOnGround() {
/* Inconsistent with Entity.isOnGround()
/ Checks to see if this player is currently standing on a block.
/ This information may not be reliable, as it is a state provided by the client, and may therefore not be accurate.
/ It can also easily be spoofed. We may want to cast to LivingEntity and call isOnGround() instead
*/
return bukkitPlayer.isOnGround();
}
@Override
public int getPing() {
return bukkitPlayer.getPing();
@@ -5,7 +5,7 @@ import org.bukkit.scheduler.BukkitTask;
import org.jetbrains.annotations.NotNull;
public class BukkitTaskHandle implements AbstractTaskHandle {
private final BukkitTask bukkitTask;
private BukkitTask bukkitTask;
private boolean cancelled;
public BukkitTaskHandle(@NotNull BukkitTask bukkitTask) {
@@ -1,36 +0,0 @@
package me.caseload.knockbacksync.sender;
import me.caseload.knockbacksync.command.generic.PlayerSelector;
import me.caseload.knockbacksync.player.BukkitPlayer;
import me.caseload.knockbacksync.player.PlatformPlayer;
import java.util.Collection;
import java.util.Collections;
public class BukkitPlayerSelectorAdapter implements PlayerSelector {
private final org.incendo.cloud.bukkit.data.SinglePlayerSelector bukkitSelector;
public BukkitPlayerSelectorAdapter(org.incendo.cloud.bukkit.data.SinglePlayerSelector bukkitSelector) {
this.bukkitSelector = bukkitSelector;
}
@Override
public boolean isSingle() {
return true;
}
@Override
public PlatformPlayer getSinglePlayer() {
return new BukkitPlayer(bukkitSelector.single());
}
@Override
public Collection<PlatformPlayer> getPlayers() {
return Collections.singletonList(new BukkitPlayer(bukkitSelector.single()));
}
@Override
public String inputString() {
return bukkitSelector.inputString();
}
}
@@ -1,28 +0,0 @@
package me.caseload.knockbacksync.sender;
import me.caseload.knockbacksync.command.generic.AbstractPlayerSelectorParser;
import me.caseload.knockbacksync.command.generic.PlayerSelector;
import org.incendo.cloud.bukkit.parser.selector.SinglePlayerSelectorParser;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.parser.ParserDescriptor;
import java.util.concurrent.CompletableFuture;
public class BukkitPlayerSelectorParser<C> extends AbstractPlayerSelectorParser<C> {
@Override
public ParserDescriptor<C, PlayerSelector> descriptor() {
return createDescriptor();
}
@Override
protected ParserDescriptor<C, ?> getPlatformSpecificDescriptor() {
return SinglePlayerSelectorParser.singlePlayerSelectorParser();
}
@Override
protected CompletableFuture<PlayerSelector> adaptToCommonSelector(CommandContext<C> context, Object platformSpecificSelector) {
return CompletableFuture.completedFuture(new BukkitPlayerSelectorAdapter((org.incendo.cloud.bukkit.data.SinglePlayerSelector) platformSpecificSelector));
}
}
@@ -1,88 +0,0 @@
package me.caseload.knockbacksync.sender;
import me.caseload.knockbacksync.KBSyncBukkitBase;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.RemoteConsoleCommandSender;
import org.bukkit.entity.Player;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.SenderMapper;
import java.util.UUID;
public class BukkitSenderFactory extends SenderFactory<KBSyncBukkitBase, CommandSender> implements SenderMapper<CommandSender, Sender> {
// private final BukkitAudiences audiences;
public BukkitSenderFactory(KBSyncBukkitBase kbSyncBukkitBase) {
super(kbSyncBukkitBase);
// this.audiences = BukkitAudiences.create(kbSyncBukkitBase.getLoader());
}
@Override
protected String getName(CommandSender sender) {
if (sender instanceof Player) {
return sender.getName();
}
return Sender.CONSOLE_NAME;
}
@Override
protected UUID getUniqueId(CommandSender sender) {
if (sender instanceof Player) {
return ((Player) sender).getUniqueId();
}
return Sender.CONSOLE_UUID;
}
@Override
protected void sendMessage(CommandSender sender, String message) {
sender.sendMessage(message);
}
// @Override
// protected void sendMessage(CommandSender sender, Component message) {
// // we can safely send async for players and the console - otherwise, send it sync
// if (sender instanceof Player || sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender) {
// this.audiences.sender(sender).sendMessage(message);
// } else {
// getPlugin().getBootstrap().getScheduler().executeSync(() -> this.audiences.sender(sender).sendMessage(message));
// }
// }
@Override
protected boolean hasPermission(CommandSender sender, String node) {
return sender.hasPermission(node);
}
@Override
protected boolean hasPermission(CommandSender sender, String node, boolean defaultIfUnset) {
return sender.hasPermission(new Permission(node, defaultIfUnset ? PermissionDefault.TRUE : PermissionDefault.FALSE));
}
@Override
protected void performCommand(CommandSender sender, String command) {
//
}
@Override
protected boolean isConsole(CommandSender sender) {
return sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender;
}
@Override
public @NonNull Sender map(@NonNull CommandSender base) {
return this.wrap(base);
}
@Override
public @NonNull CommandSender reverse(@NonNull Sender mapped) {
return this.unwrap(mapped);
}
@Override
public void close() throws Exception {
}
}
@@ -15,8 +15,8 @@ package me.caseload.knockbacksync.stats.custom;
* Violations will result in a ban of your plugin and account from bStats.
*/
import me.caseload.knockbacksync.KBSyncBukkitLoaderPlugin;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.KnockbackSyncPlugin;
import me.caseload.knockbacksync.stats.CustomChart;
import me.caseload.knockbacksync.stats.JsonObjectBuilder;
import org.bukkit.Bukkit;
@@ -39,7 +39,7 @@ public class MetricsBukkit implements Metrics {
public MetricsBukkit(int serviceId) {
this.plugin = KBSyncBukkitLoaderPlugin.getPlugin(KBSyncBukkitLoaderPlugin.class);
this.plugin = KnockbackSyncPlugin.getPlugin(KnockbackSyncPlugin.class);
// Get the config file
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
File configFile = new File(bStatsFolder, "config.yml");
+1 -1
View File
@@ -1,6 +1,6 @@
name: KnockbackSync
version: ${version}
main: me.caseload.knockbacksync.KBSyncBukkitLoaderPlugin
main: me.caseload.knockbacksync.KnockbackSyncPlugin
api-version: '1.16'
authors: [ Caseload ]
description: Synchronizes player knockback for smoother gameplay.
+20 -36
View File
@@ -23,41 +23,7 @@ dependencies {
exclude(group = "commons-io", module = "commons-io")
exclude(group = "org.apache.commons", module = "commons-lang3")
}
api("net.kyori:adventure-api:4.11.0") {
exclude(module = "adventure-bom")
exclude(module = "checker-qual")
exclude(module = "annotations")
}
api("net.kyori:adventure-text-serializer-gson:4.11.0") {
exclude(module = "adventure-bom")
exclude(module = "adventure-api")
exclude(module = "gson")
}
api("net.kyori:adventure-text-serializer-legacy:4.11.0") {
exclude(module = "adventure-bom")
exclude(module = "adventure-api")
}
api("net.kyori:adventure-text-serializer-plain:4.11.0") {
exclude(module = "adventure-bom")
exclude(module = "adventure-api")
}
api("net.kyori:adventure-text-minimessage:4.11.0") {
exclude(module = "adventure-bom")
exclude(module = "adventure-api")
}
api("net.kyori:event-api:3.0.0") {
exclude(module = "checker-qual")
exclude(module = "guava")
}
implementation("org.incendo:cloud-core:2.0.0")
implementation("org.incendo:cloud-minecraft-extras:2.0.0-beta.10")
implementation("dev.jorel:commandapi-bukkit-shade:9.5.3")
}
repositories {
@@ -66,4 +32,22 @@ repositories {
tasks.named("remapJar").configure {
enabled = false
}
}
// Using neoforge in vanilla mode so common code compiles
//neoForge {
// Look for versions on https://projects.neoforged.net/neoforged/neoform
// neoFormVersion.set("1.21-20240613.152323")
// runs {
// create("client") {
// client()
// }
// create("server") {
// server()
// }
// create("data") {
// data()
// }
// }
//}
@@ -3,9 +3,7 @@ package me.caseload.knockbacksync;
import me.caseload.knockbacksync.util.NumberConversions;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class ConfigWrapper {
private final Map<String, Object> configMap;
@@ -63,20 +61,4 @@ public class ConfigWrapper {
}
current.put(parts[parts.length - 1], value);
}
public boolean contains(String path) {
return getValue(path) != null;
}
public Object get(String path) {
return getValue(path);
}
public Set<String> getKeys(String path) {
Object value = getValue(path);
if (value instanceof Map) {
return ((Map<String, Object>) value).keySet();
}
return new HashSet<>();
}
}
@@ -2,50 +2,43 @@ package me.caseload.knockbacksync;
import com.github.retrooper.packetevents.PacketEvents;
import lombok.Getter;
import me.caseload.knockbacksync.command.MainCommand;
import me.caseload.knockbacksync.command.generic.AbstractPlayerSelectorParser;
import me.caseload.knockbacksync.command.generic.BuilderCommand;
import me.caseload.knockbacksync.command.subcommand.PingCommand;
import me.caseload.knockbacksync.command.subcommand.ReloadCommand;
import me.caseload.knockbacksync.command.subcommand.StatusCommand;
import me.caseload.knockbacksync.command.subcommand.ToggleCommand;
import lombok.Setter;
import me.caseload.knockbacksync.command.SenderFactory;
import me.caseload.knockbacksync.listener.packetevents.AttributeChangeListener;
import me.caseload.knockbacksync.listener.packetevents.PingReceiveListener;
import me.caseload.knockbacksync.manager.ConfigManager;
import me.caseload.knockbacksync.permission.PermissionChecker;
import me.caseload.knockbacksync.scheduler.SchedulerAdapter;
import me.caseload.knockbacksync.sender.Sender;
import me.caseload.knockbacksync.stats.custom.PluginJarHashProvider;
import me.caseload.knockbacksync.stats.custom.StatsManager;
import me.caseload.knockbacksync.world.PlatformServer;
import org.incendo.cloud.CommandManager;
import org.kohsuke.github.GitHub;
import java.io.File;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
// Base class
public abstract class KnockbackSyncBase {
public static Logger LOGGER;
public static KnockbackSyncBase INSTANCE;
public final Platform platform;
public Platform platform;
public StatsManager statsManager;
public PlatformServer platformServer;
@Getter protected PluginJarHashProvider pluginJarHashProvider;
@Getter protected SchedulerAdapter scheduler;
@Getter protected ConfigManager configManager;
protected CommandManager<Sender> commandManager;
public PluginJarHashProvider pluginJarHashProvider;
@Getter
protected AbstractPlayerSelectorParser<Sender> playerSelectorParser;
protected SchedulerAdapter scheduler;
@Getter
protected ConfigManager configManager;
@Setter
@Getter
private SenderFactory<? extends KnockbackSyncBase, ?> senderFactory;
protected KnockbackSyncBase() {
this.platform = getPlatform();
INSTANCE = this;
configManager = new ConfigManager();
}
private Platform getPlatform() {
@@ -106,17 +99,7 @@ public abstract class KnockbackSyncBase {
protected abstract void registerPlatformListeners();
protected void registerCommands() {
List<BuilderCommand> list = Arrays.asList(
new MainCommand(),
new ReloadCommand(),
new PingCommand(),
new StatusCommand(),
new ToggleCommand()
);
list.forEach(command -> command.register(commandManager));
}
protected abstract void registerCommands();
protected abstract String getVersion();
@@ -1,15 +1,15 @@
package me.caseload.knockbacksync.sender;
package me.caseload.knockbacksync.command;
import me.caseload.knockbacksync.KnockbackSyncBase;
import java.util.UUID;
/**
* Simple implementation of {@link Sender} using a {@link SenderFactory}
* Simple implementation of {@link PlatformSender} using a {@link SenderFactory}
*
* @param <T> the command sender type
*/
public final class AbstractSender<T> implements Sender {
public final class AbstractSender<T> implements PlatformSender {
private final KnockbackSyncBase plugin;
private final SenderFactory<?, T> factory;
private final T sender;
@@ -52,11 +52,6 @@ public final class AbstractSender<T> implements Sender {
return isConsole() || this.factory.hasPermission(this.sender, permission);
}
@Override
public boolean hasPermission(String permission, boolean defaultIfUnset) {
return isConsole() || this.factory.hasPermission(this.sender, permission, defaultIfUnset);
}
@Override
public void performCommand(String commandLine) {
this.factory.performCommand(this.sender, commandLine);
@@ -0,0 +1,4 @@
package me.caseload.knockbacksync.command;
public interface CommandSource {
}
@@ -1,22 +0,0 @@
package me.caseload.knockbacksync.command;
import me.caseload.knockbacksync.command.generic.BuilderCommand;
import me.caseload.knockbacksync.sender.Sender;
import me.caseload.knockbacksync.util.ChatUtil;
import org.incendo.cloud.CommandManager;
public class MainCommand implements BuilderCommand {
public void register(CommandManager<Sender> manager) {
manager.command(
manager.commandBuilder("knockbacksync", "kbsync", "kbs")
.handler(context -> {
context.sender().sendMessage(
ChatUtil.translateAlternateColorCodes(
'&',
"&6This server is running the &eKnockbackSync &6plugin. &bhttps://github.com/CASELOAD7000/knockback-sync"
)
);
})
);
}
}
@@ -1,4 +1,4 @@
package me.caseload.knockbacksync.sender;
package me.caseload.knockbacksync.command;
import me.caseload.knockbacksync.KnockbackSyncBase;
@@ -7,7 +7,7 @@ import java.util.UUID;
/**
* Wrapper interface to represent a CommandSender/CommandSource within the common command implementations.
*/
public interface Sender {
public interface PlatformSender {
/**
* The uuid used by the console sender.
@@ -63,8 +63,6 @@ public interface Sender {
*/
boolean hasPermission(String permission);
boolean hasPermission(String permission, boolean defaultIfUnset);
/**
* Makes the sender perform a command.
*
@@ -1,4 +1,4 @@
package me.caseload.knockbacksync.sender;
package me.caseload.knockbacksync.command;
import me.caseload.knockbacksync.KnockbackSyncBase;
@@ -30,8 +30,6 @@ public abstract class SenderFactory<P extends KnockbackSyncBase, T> implements A
protected abstract boolean hasPermission(T sender, String node);
protected abstract boolean hasPermission(T sender, String node, boolean defaultIfUnset);
protected abstract void performCommand(T sender, String command);
protected abstract boolean isConsole(T sender);
@@ -40,14 +38,13 @@ public abstract class SenderFactory<P extends KnockbackSyncBase, T> implements A
return isConsole(sender);
}
public final Sender wrap(T sender) {
public final PlatformSender wrap(T sender) {
Objects.requireNonNull(sender, "sender");
return new AbstractSender<>(this.plugin, this, sender);
}
@SuppressWarnings("unchecked")
public final T unwrap(Sender sender) {
Objects.requireNonNull(sender, "sender");
return (T) sender.getSender();
@Override
public void close() {
}
}
@@ -1,23 +0,0 @@
package me.caseload.knockbacksync.command.generic;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.parser.ParserDescriptor;
import java.util.concurrent.CompletableFuture;
public abstract class AbstractPlayerSelectorParser<C> {
public abstract ParserDescriptor<C, PlayerSelector> descriptor();
protected abstract ParserDescriptor<C, ?> getPlatformSpecificDescriptor();
protected abstract CompletableFuture<PlayerSelector> adaptToCommonSelector(CommandContext<C> context, Object platformSpecificSelector);
// Helper method to create the ParserDescriptor
protected ParserDescriptor<C, PlayerSelector> createDescriptor() {
return ParserDescriptor.of(
getPlatformSpecificDescriptor().parser().mapSuccess(this::adaptToCommonSelector),
PlayerSelector.class
);
}
}
@@ -1,9 +0,0 @@
package me.caseload.knockbacksync.command.generic;
import me.caseload.knockbacksync.sender.Sender;
import org.incendo.cloud.CommandManager;
public interface BuilderCommand {
void register(CommandManager<Sender> manager);
}
@@ -1,15 +0,0 @@
package me.caseload.knockbacksync.command.generic;
import me.caseload.knockbacksync.player.PlatformPlayer;
import java.util.Collection;
public interface PlayerSelector {
boolean isSingle();
PlatformPlayer getSinglePlayer(); // Throws an exception if not a single selection
Collection<PlatformPlayer> getPlayers();
String inputString();
}
@@ -1,42 +0,0 @@
package me.caseload.knockbacksync.command.subcommand;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.command.generic.BuilderCommand;
import me.caseload.knockbacksync.command.generic.PlayerSelector;
import me.caseload.knockbacksync.sender.Sender;
import me.caseload.knockbacksync.util.ChatUtil;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.permission.PredicatePermission;
import java.util.function.Predicate;
public class PingCommand implements BuilderCommand {
public void register(CommandManager<Sender> manager) {
manager.command(
manager.commandBuilder("knockbacksync", "kbsync", "kbs")
.permission((sender -> {
final String permission = "knockbacksync.ping";
Predicate<Sender> senderPredicate = (s) -> {
return s.hasPermission(permission, true);
};
return PredicatePermission.of(senderPredicate).testPermission(sender);
}))
.literal("ping")
.optional("target", KnockbackSyncBase.INSTANCE.getPlayerSelectorParser().descriptor())
.handler(context -> {
PlayerSelector targetSelector = context.getOrDefault("target", null);
if (targetSelector == null) {
if (context.sender().isConsole()) {
context.sender().sendMessage("You must specify a player to use this command from the console.");
} else {
context.sender().sendMessage(ChatUtil.getPingMessage(context.sender().getUniqueId(), null));
}
} else {
context.sender().sendMessage(ChatUtil.getPingMessage(context.sender().getUniqueId(), targetSelector.getSinglePlayer().getUUID()));
}
})
);
}
}
@@ -1,37 +0,0 @@
package me.caseload.knockbacksync.command.subcommand;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.command.generic.BuilderCommand;
import me.caseload.knockbacksync.manager.ConfigManager;
import me.caseload.knockbacksync.sender.Sender;
import me.caseload.knockbacksync.util.ChatUtil;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.permission.PredicatePermission;
import java.util.function.Predicate;
public class ReloadCommand implements BuilderCommand {
public void register(CommandManager<Sender> manager) {
manager.command(
manager.commandBuilder("knockbacksync", "kbsync", "kbs")
.literal("reload")
.permission((sender -> {
final String permission = "knockbacksync.reload";
Predicate<Sender> senderPredicate = (s) -> {
return s.hasPermission(permission, false);
};
return PredicatePermission.of(senderPredicate).testPermission(sender);
}))
.handler(context -> {
ConfigManager configManager = KnockbackSyncBase.INSTANCE.getConfigManager();
configManager.loadConfig(true);
String rawReloadMessage = configManager.getReloadMessage();
String reloadMessage = ChatUtil.translateAlternateColorCodes('&', rawReloadMessage);
context.sender().sendMessage(reloadMessage);
})
);
}
}
@@ -1,87 +0,0 @@
package me.caseload.knockbacksync.command.subcommand;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.command.generic.BuilderCommand;
import me.caseload.knockbacksync.command.generic.PlayerSelector;
import me.caseload.knockbacksync.manager.ConfigManager;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.player.PlatformPlayer;
import me.caseload.knockbacksync.sender.Sender;
import me.caseload.knockbacksync.util.ChatUtil;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.permission.PredicatePermission;
import java.util.UUID;
import java.util.function.Predicate;
public class StatusCommand implements BuilderCommand {
private static final ConfigManager configManager = KnockbackSyncBase.INSTANCE.getConfigManager();
private static final String STATUS_SELF_PERMISSION = "knockbacksync.status.self";
private static final String STATUS_OTHER_PERMISSION = "knockbacksync.status.other";
public void register(CommandManager<Sender> manager) {
manager.command(
manager.commandBuilder("knockbacksync", "kbsync", "kbs")
.literal("status")
.optional("target", KnockbackSyncBase.INSTANCE.getPlayerSelectorParser().descriptor())
.permission((sender -> {
Predicate<Sender> senderPredicate = (s) -> {
return s.hasPermission(STATUS_SELF_PERMISSION, true) || sender.hasPermission(STATUS_OTHER_PERMISSION, false);
};
return PredicatePermission.of(senderPredicate).testPermission(sender);
}))
.handler(context -> {
Sender sender = context.sender();
PlayerSelector targetSelector = context.getOrDefault("target", null);
if (targetSelector == null) {
// Show global status
boolean globalStatus = configManager.isToggled();
sender.sendMessage(ChatUtil.translateAlternateColorCodes('&',
configManager.getConfigWrapper().getString("global_status_message", "&eGlobal KnockbackSync status: ")) +
(globalStatus
? ChatUtil.translateAlternateColorCodes('&', "&aEnabled")
: ChatUtil.translateAlternateColorCodes('&', "&cDisabled")));
if (sender.hasPermission(STATUS_SELF_PERMISSION, true)) {
// Show player status for the sender (no target specified)
if (!sender.isConsole()) {
showPlayerStatus(sender, KnockbackSyncBase.INSTANCE.platformServer.getPlayer(sender.getUniqueId()), configManager);
}
} else {
sender.sendMessage(ChatUtil.translateAlternateColorCodes('&', "&cYou do not have permisssion to check your knockbacksync status."));
}
} else {
if (sender.hasPermission(STATUS_OTHER_PERMISSION)) {
PlatformPlayer target = targetSelector.getSinglePlayer();
showPlayerStatus(sender, target, configManager);
} else {
sender.sendMessage(ChatUtil.translateAlternateColorCodes('&', "&cYou do not have permisssion to check the status of other players!"));
}
}
})
);
}
private static void showPlayerStatus(Sender sender, PlatformPlayer target, ConfigManager configManager) {
boolean globalStatus = configManager.isToggled();
UUID uuid = target.getUUID();
boolean playerStatus = PlayerDataManager.containsPlayerData(uuid);
if (!globalStatus) {
sender.sendMessage(ChatUtil.translateAlternateColorCodes('&',
configManager.getConfigWrapper().getString("player_status_message", "&e%player%'s KnockbackSync status: ")
.replace("%player%", target.getName()) +
ChatUtil.translateAlternateColorCodes('&',
configManager.getConfigWrapper().getString("player_disabled_global_message", "&cDisabled (Global toggle is off)"))));
} else {
sender.sendMessage(ChatUtil.translateAlternateColorCodes('&',
configManager.getConfigWrapper().getString("player_status_message", "&e%player%'s KnockbackSync status: ")
.replace("%player%", target.getName()) +
(playerStatus
? ChatUtil.translateAlternateColorCodes('&', "&aEnabled")
: ChatUtil.translateAlternateColorCodes('&', "&cDisabled"))));
}
}
}
@@ -1,114 +0,0 @@
package me.caseload.knockbacksync.command.subcommand;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.command.generic.BuilderCommand;
import me.caseload.knockbacksync.command.generic.PlayerSelector;
import me.caseload.knockbacksync.manager.ConfigManager;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.permission.PermissionChecker;
import me.caseload.knockbacksync.player.PlatformPlayer;
import me.caseload.knockbacksync.player.PlayerData;
import me.caseload.knockbacksync.sender.Sender;
import me.caseload.knockbacksync.util.ChatUtil;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.permission.Permission;
import org.incendo.cloud.permission.PredicatePermission;
import java.util.UUID;
import java.util.function.Predicate;
public class ToggleCommand implements BuilderCommand {
private static final ConfigManager configManager = KnockbackSyncBase.INSTANCE.getConfigManager();
private static final PermissionChecker permissionChecker = KnockbackSyncBase.INSTANCE.getPermissionChecker();
private static final String TOGGLE_GLOBAL_PERMISSION = "knockbacksync.toggle.global";
private static final String TOGGLE_SELF_PERMISSION = "knockbacksync.toggle.self";
private static final String TOGGLE_OTHER_PERMISSION = "knockbacksync.toggle.other";
@Override
public void register(CommandManager<Sender> manager) {
manager.command(
manager.commandBuilder("knockbacksync", "kbsync", "kbs")
.literal("toggle")
.optional("target", KnockbackSyncBase.INSTANCE.getPlayerSelectorParser().descriptor())
.permission((sender -> {
Predicate<Sender> senderPredicate = (s) -> {
return s.hasPermission(TOGGLE_GLOBAL_PERMISSION, false)
|| sender.hasPermission(TOGGLE_SELF_PERMISSION, true)
|| sender.hasPermission(TOGGLE_OTHER_PERMISSION, false);
};
return PredicatePermission.of(senderPredicate).testPermission(sender);
}))
.handler(context -> {
Sender sender = context.sender();
PlayerSelector targetSelector = context.getOrDefault("target", null);
if (targetSelector == null) {
// Global toggle
if (permissionChecker.hasPermission(sender, TOGGLE_GLOBAL_PERMISSION, false)) {
toggleGlobalKnockback(configManager, sender);
} else {
sender.sendMessage(ChatUtil.translateAlternateColorCodes('&', "&cYou don't have permission to toggle the global setting."));
}
} else {
PlatformPlayer target = targetSelector.getSinglePlayer();
boolean senderIsTarget = sender.getUniqueId() == target.getUUID();
if (!senderIsTarget && !permissionChecker.hasPermission(sender, TOGGLE_OTHER_PERMISSION, false)) {
sender.sendMessage("You do not have permission to toggle the knockback of other player's.");
return;
} else if (senderIsTarget && !permissionChecker.hasPermission(sender, TOGGLE_SELF_PERMISSION, true)) {
sender.sendMessage("You do not have permission to toggle your knockback.");
return;
}
if (!configManager.isToggled()) {
sender.sendMessage(ChatUtil.translateAlternateColorCodes('&', "&cKnockbacksync is currently disabled on this server. Contact your server administrator for more information."));
} else {
togglePlayerKnockback(target, configManager, sender);
}
}
})
);
}
private static void toggleGlobalKnockback(ConfigManager configManager, Sender sender) {
boolean toggledState = !configManager.isToggled();
configManager.setToggled(toggledState);
KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper().set("enabled", toggledState);
KnockbackSyncBase.INSTANCE.getConfigManager().saveConfig();
String message = ChatUtil.translateAlternateColorCodes('&',
toggledState ? configManager.getEnableMessage() : configManager.getDisableMessage()
);
sender.sendMessage(ChatUtil.translateAlternateColorCodes('&', message));
}
private static void togglePlayerKnockback(PlatformPlayer target, ConfigManager configManager, Sender sender) {
UUID uuid = target.getUUID();
if (PlayerDataManager.shouldExempt(uuid)) {
String message = ChatUtil.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(KnockbackSyncBase.INSTANCE.platformServer.getPlayer(uuid)));
}
String message = ChatUtil.translateAlternateColorCodes('&',
hasPlayerData ? configManager.getPlayerDisableMessage() : configManager.
getPlayerEnableMessage()
).replace("%player%", target.getName());
sender.sendMessage(message);
}
}
@@ -1,39 +0,0 @@
package me.caseload.knockbacksync.command.subcommand;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.command.generic.BuilderCommand;
import me.caseload.knockbacksync.permission.PermissionChecker;
import me.caseload.knockbacksync.sender.Sender;
import me.caseload.knockbacksync.util.ChatUtil;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.permission.PredicatePermission;
import java.util.function.Predicate;
public class ToggleOffGroundSubcommand implements BuilderCommand {
@Override
public void register(CommandManager<Sender> manager) {
manager.command(
manager.commandBuilder("knockbacksync", "kbsync", "kbs")
.literal("toggleoffground")
.permission((sender -> {
final String permission = "knockbacksync.toggleoffground";
Predicate<Sender> senderPredicate = (s) -> {
return s.hasPermission(permission, false);
};
return PredicatePermission.of(senderPredicate).testPermission(sender);
}))
.handler(commandContext -> {
boolean toggledState = !KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper().getBoolean("enable_experimental_offground", false);
KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper().set("enable_experimental_offground", toggledState);
KnockbackSyncBase.INSTANCE.getConfigManager().saveConfig();
String message = ChatUtil.translateAlternateColorCodes('&',
toggledState ?
KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper().getString("enable_experimental_offground_message", "&aSuccessfully enabled offground experiment.") :
KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper().getString("disable_experimental_offground_message", "&cSuccessfully disabled offground experiment."));
commandContext.sender().sendMessage(message);
}));
}
}
@@ -18,9 +18,9 @@ public class AttributeChangeListener extends PacketListenerAbstract {
public static final UUID SPRINTING_MODIFIER_UUID =
UUID.fromString("662A6B8D-DA3E-4C1C-8813-96EA6097278D");
final double minGravity = -1;
final double maxGravity = 1;
final double defaultGravity = 0.08;
double minGravity = -1;
double maxGravity = 1;
double defaultGravity = 0.08;
double currentGravity;
public AttributeChangeListener() {
@@ -2,102 +2,42 @@ package me.caseload.knockbacksync.listener.packetevents;
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientKeepAlive;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPong;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientWindowConfirmation;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerKeepAlive;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPing;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWindowConfirmation;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.player.PlayerData;
import me.caseload.knockbacksync.util.data.Pair;
import java.util.List;
public class PingReceiveListener extends PacketListenerAbstract {
// TODO supplment our own ping calculations with over keepalive sent by the server with
@Override
public void onPacketSend(PacketSendEvent event) {
PacketTypeCommon packetType = event.getPacketType();
if (packetType.equals(PacketType.Play.Server.KEEP_ALIVE)) {
WrapperPlayServerKeepAlive keepAlive = new WrapperPlayServerKeepAlive(event);
long id = keepAlive.getId();
PlayerData playerData = PlayerDataManager.getPlayerData(event.getUser().getUUID());
if (playerData == null) return;
playerData.keepaliveMap.add(new Pair<>(id, System.nanoTime()));
} else if (packetType.equals(PacketType.Play.Server.PING)) {
WrapperPlayServerPing ping = new WrapperPlayServerPing(event);
int id = ping.getId();
PlayerData playerData = PlayerDataManager.getPlayerData(event.getUser().getUUID());
if (playerData == null) return;
playerData.transactionsSent.add(new Pair<>(id, System.nanoTime()));
} else if (packetType.equals(PacketType.Play.Server.WINDOW_CONFIRMATION)) {
WrapperPlayServerWindowConfirmation confirmation = new WrapperPlayServerWindowConfirmation(event);
int id = confirmation.getActionId();
PlayerData playerData = PlayerDataManager.getPlayerData(event.getUser().getUUID());
if (playerData == null) return;
playerData.transactionsSent.add(new Pair<>(id, System.nanoTime()));
}
}
// @Override
// public void onPacketSend(PacketSendEvent event) {
// if (event.getPacketType() == PacketType.Play.Server.KEEP_ALIVE) {
// }
// }
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (!KnockbackSyncBase.INSTANCE.getConfigManager().isToggled()) return;
if (event.getPacketType() != PacketType.Play.Client.KEEP_ALIVE) return;
PacketTypeCommon packetType = event.getPacketType();
if (packetType == PacketType.Play.Client.KEEP_ALIVE) {
WrapperPlayClientKeepAlive keepAlive = new WrapperPlayClientKeepAlive(event);
PlayerData playerData = PlayerDataManager.getPlayerData(event.getUser().getUUID());
if (playerData == null) return;
WrapperPlayClientKeepAlive keepAlive = new WrapperPlayClientKeepAlive(event);
PlayerData playerData = PlayerDataManager.getPlayerData(event.getUser().getUUID());
if (playerData == null) return;
long receivedId = keepAlive.getId();
long receivedId = keepAlive.getId();
if (playerData.isKeepAliveIDOurs(receivedId)) {
long pingNanos = (System.nanoTime() - receivedId);
// Calculate the remainder nanoseconds for fractional milliseconds
double diffMillisDouble = pingNanos / 1_000_000.0;
handlePingCalculationPackets(event, playerData, receivedId, playerData.keepaliveMap);
} else if (packetType == PacketType.Play.Client.PONG) {
WrapperPlayClientPong pong = new WrapperPlayClientPong(event);
PlayerData playerData = PlayerDataManager.getPlayerData(event.getUser().getUUID());
if (playerData == null) return;
playerData.setPreviousPing(playerData.getPing());
playerData.setPing(diffMillisDouble);
int id = pong.getId();
handlePingCalculationPackets(event, playerData, id, playerData.transactionsSent);
} else if (packetType == PacketType.Play.Client.WINDOW_CONFIRMATION) {
WrapperPlayClientWindowConfirmation windowConfirmation = new WrapperPlayClientWindowConfirmation(event);
PlayerData playerData = PlayerDataManager.getPlayerData(event.getUser().getUUID());
if (playerData == null) return;
int id = windowConfirmation.getActionId();
handlePingCalculationPackets(event, playerData, id, playerData.transactionsSent);
}
}
private <T extends Number> void handlePingCalculationPackets(PacketReceiveEvent event, PlayerData playerData, long id, List<Pair<T, Long>> packetSentList) {
long pingNanos = (System.nanoTime() - packetSentList.remove(0).getSecond());
double diffMillisDouble = pingNanos / 1_000_000.0;
playerData.setPreviousPing(playerData.getPing());
playerData.setPing(diffMillisDouble);
playerData.getJitterCalculator().addPing(pingNanos);
double jitter = playerData.getJitterCalculator().calculateJitter();
playerData.setJitter(jitter);
// We can cancel for all 3 cases
if (playerData.didWeSendThatPacket(id)) {
// Pong not needed as vanilla ignores the packet, its needed for packet limiters
// We want to cancel Window Confirmations that we send, and ...
playerData.getJitterCalculator().addPing(pingNanos);
double jitter = playerData.getJitterCalculator().calculateJitter();
playerData.setJitter(jitter);
// Minecraft kicks players that send invalid keepAliveID packets
// Since Minecraft doesn't know we just sent a keepalive, we gotta cancel it
// To stop MC from processing it and kicking the player
@@ -7,6 +7,7 @@ import lombok.Setter;
import me.caseload.knockbacksync.ConfigWrapper;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Platform;
import me.caseload.knockbacksync.player.PlayerData;
import me.caseload.knockbacksync.runnable.PingRunnable;
import me.caseload.knockbacksync.scheduler.AbstractTaskHandle;
@@ -19,8 +20,6 @@ import java.util.Map;
@Setter
public class ConfigManager {
public static final long CONFIG_VERSION = 2;
private boolean toggled;
private boolean runnableEnabled;
private boolean updateAvailable;
@@ -118,54 +117,6 @@ public class ConfigManager {
playerDisableMessage = config.getString("player_disable_message", "&cSuccessfully disabled KnockbackSync for %player%.");
playerIneligibleMessage = config.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 = config.getString("reload_message", "&aSuccessfully reloaded KnockbackSync.");
}
public void updateConfig() {
ConfigWrapper oldConfig = getConfigWrapper();
long oldConfigVersion = oldConfig.getLong("config_version", 0);
if (oldConfigVersion < CONFIG_VERSION) {
// Backup old config
File backupFile = new File(configFile.getParentFile(), "config-version-" + oldConfigVersion + ".yml");
if (configFile.renameTo(backupFile)) {
KnockbackSyncBase.INSTANCE.getLogger().info("Backed up old config to " + backupFile.getName());
} else {
KnockbackSyncBase.INSTANCE.getLogger().warning("Failed to backup old config");
}
// Create new config with default values
KnockbackSyncBase.INSTANCE.saveDefaultConfig();
reloadConfig();
// Transfer existing settings
ConfigWrapper newConfig = getConfigWrapper();
transferAllSettings(oldConfig, newConfig);
// Set new config version
newConfig.set("config_version", CONFIG_VERSION);
// Save updated config
saveConfig();
KnockbackSyncBase.INSTANCE.getLogger().info("Config updated to version " + CONFIG_VERSION);
}
}
private void transferAllSettings(ConfigWrapper oldConfig, ConfigWrapper newConfig) {
transferSettingsRecursive(oldConfig, newConfig, "");
}
private void transferSettingsRecursive(ConfigWrapper oldConfig, ConfigWrapper newConfig, String currentPath) {
for (String key : oldConfig.getKeys(currentPath)) {
String fullPath = currentPath.isEmpty() ? key : currentPath + "." + key;
if (newConfig.contains(fullPath)) {
Object value = oldConfig.get(fullPath);
if (value instanceof Map) {
transferSettingsRecursive(oldConfig, newConfig, fullPath);
} else {
newConfig.set(fullPath, value);
}
}
}
PlayerData.PING_OFFSET = config.getInt("ping_offset", 25);
}
}
@@ -1,12 +1,12 @@
package me.caseload.knockbacksync.permission;
import me.caseload.knockbacksync.command.PlatformSender;
import me.caseload.knockbacksync.player.PlatformPlayer;
import me.caseload.knockbacksync.sender.Sender;
public interface PermissionChecker {
boolean hasPermission(Object nativeType, String s, boolean defaultIfUnset);
boolean hasPermission(Object CommandSourceStack, String s, boolean defaultIfUnset);
boolean hasPermission(Sender source, String s, boolean defaultIfUnset);
boolean hasPermission(PlatformSender source, String s, boolean defaultIfUnset);
boolean hasPermission(PlatformPlayer platform, String s);
}
@@ -1,7 +1,6 @@
package me.caseload.knockbacksync.player;
import java.util.*;
import java.util.stream.Collectors;
public class JitterCalculator {
private final int SAMPLE_SIZE = 15;
@@ -32,7 +31,7 @@ public class JitterCalculator {
double upperBound = q3 + 1.5 * iqr;
List<Long> filteredPings = sortedPings.stream()
.filter(p -> p >= lowerBound && p <= upperBound)
.collect(Collectors.toList());
.toList();
// Calculate standard deviation
double mean = filteredPings.stream().mapToLong(Long::longValue).average().orElse(0);
@@ -2,7 +2,6 @@ package me.caseload.knockbacksync.player;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.player.PlayerManager;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.netty.channel.ChannelHelper;
import com.github.retrooper.packetevents.protocol.ConnectionState;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
@@ -10,10 +9,7 @@ import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerKeepAlive;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPing;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWindowConfirmation;
import lombok.Getter;
import lombok.Setter;
import me.caseload.knockbacksync.KnockbackSyncBase;
@@ -24,27 +20,22 @@ import me.caseload.knockbacksync.util.data.Pair;
import me.caseload.knockbacksync.world.PlatformWorld;
import me.caseload.knockbacksync.world.raytrace.FluidHandling;
import me.caseload.knockbacksync.world.raytrace.RayTraceResult;
import org.incendo.cloud.CloudCapability;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import java.util.UUID;
@Getter
public class PlayerData {
// Please read the GitHub FAQ before adjusting.
public static final long PING_OFFSET = 25;
public static long PING_OFFSET;
public static float TICK_RATE = 20.0F;
private static Field playerField;
public final List<Pair<Integer, Long>> transactionsSent = new LinkedList<>();
public final List<Pair<Long, Long>> keepaliveMap = new LinkedList<>();
private static final short MAIN_THREAD_TRANSACTION_ID = -1;
private static final short NETTY_THREAD_TRANSACTION_ID = -2;
static {
try {
@@ -70,16 +61,30 @@ public class PlayerData {
public final User user;
private final PlatformPlayer platformPlayer;
private final UUID uuid;
@NotNull private final Random random = new Random();
@NotNull
private final Random random = new Random();
public Queue<Pair<Long, Long>> keepaliveMap = new LinkedList<>();
public long lastKeepAliveID = 0;
@Getter private final JitterCalculator jitterCalculator = new JitterCalculator();
@Setter private double jitter;
@Nullable private AbstractTaskHandle combatTask;
@Nullable @Setter private Double ping, previousPing;
@Nullable @Setter private Double verticalVelocity;
@Nullable @Setter private Integer lastDamageTicks;
@Setter private double gravityAttribute = 0.08;
@Setter private double knockbackResistanceAttribute = 0.0;
@Getter
private JitterCalculator jitterCalculator = new JitterCalculator();
@Getter
@Setter
private double jitter;
@Nullable
private AbstractTaskHandle combatTask;
@Nullable
@Setter
private Double ping, previousPing;
@Nullable
@Setter
private Double verticalVelocity;
@Nullable
@Setter
private Integer lastDamageTicks;
@Setter
private double gravityAttribute = 0.08;
@Setter
private double knockbackResistanceAttribute = 0.0;
public PlayerData(PlatformPlayer platformPlayer) {
this.uuid = platformPlayer.getUUID();
@@ -117,47 +122,24 @@ public class PlayerData {
return Math.max(1, ping - PING_OFFSET);
}
// public boolean isKeepAliveIDOurs(long id) {
// return id == lastKeepAliveID;
// }
public boolean isKeepAliveIDOurs(long id) {
return id == lastKeepAliveID;
}
// Doesn't actually send a ping, sends a keepalive, more accurate and processed faster
public void sendPing(boolean async) {
if (user == null || user.getEncoderState() != ConnectionState.PLAY) return;
String pingStrategy = KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper().getString("ping_strategy", "KEEPALIVE");
switch (pingStrategy) {
case "KEEPALIVE":
long keepAliveID = async ? NETTY_THREAD_TRANSACTION_ID : MAIN_THREAD_TRANSACTION_ID;
if (async) {
ChannelHelper.runInEventLoop(user.getChannel(), () -> {
// We call sendPacket instead of writePacket because it flushes immediately
// Making our time measurement more accurate since we don't call, System.nanoTime(), wait until flush
// And then actually send packet
user.sendPacket(new WrapperPlayServerKeepAlive(keepAliveID));
});
} else {
user.sendPacket(new WrapperPlayServerKeepAlive(keepAliveID));
}
break;
case "PING":
case "TRANSACTION":
PacketWrapper<?> packet;
short pingTransactionID = async ? NETTY_THREAD_TRANSACTION_ID : MAIN_THREAD_TRANSACTION_ID;
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_17)) {
packet = new WrapperPlayServerPing(pingTransactionID);
} else {
packet = new WrapperPlayServerWindowConfirmation((byte) 0, pingTransactionID, false);
}
if (async) {
ChannelHelper.runInEventLoop(user.getChannel(), () -> {
user.writePacket(packet);
});
} else {
user.writePacket(packet);
}
break;
}
if (async) {
ChannelHelper.runInEventLoop(user.getChannel(), () -> {
// We call sendPacket instead of writePacket because it flushes immediately
// Making our time measurement more accurate since we don't call, System.nanoTime(), wait until flush
// And then actually send packet
user.sendPacket(new WrapperPlayServerKeepAlive(lastKeepAliveID = System.nanoTime()));
});
} else {
user.sendPacket(new WrapperPlayServerKeepAlive(lastKeepAliveID = System.nanoTime()));
}
}
/**
@@ -323,8 +305,4 @@ public class PlayerData {
public long getKeepAliveSendTime() {
return lastKeepAliveID;
}
public boolean didWeSendThatPacket(long receivedId) {
return receivedId == NETTY_THREAD_TRANSACTION_ID || receivedId == MAIN_THREAD_TRANSACTION_ID;
}
}
@@ -2,7 +2,7 @@ package me.caseload.knockbacksync.scheduler;
public interface AbstractTaskHandle {
boolean getCancelled();
public boolean getCancelled();
void cancel();
public void cancel();
}
@@ -8,8 +8,12 @@ import org.kohsuke.github.GHAsset;
import org.kohsuke.github.GHRelease;
import org.kohsuke.github.GitHub;
import java.io.*;
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.util.List;
@@ -35,7 +39,7 @@ public class BuildTypePie extends SimplePie {
private static String calculateBuildType() {
try {
String currentHash = KnockbackSyncBase.INSTANCE.getPluginJarHashProvider().getPluginJarHash();
String currentHash = KnockbackSyncBase.INSTANCE.pluginJarHashProvider.getPluginJarHash();
downloadBuildFiles();
if (isHashInFile(currentHash, new File(dataFolder, RELEASES_FILE))) {
@@ -53,7 +57,7 @@ public class BuildTypePie extends SimplePie {
private static void downloadBuildFiles() throws IOException {
GitHub gitHub = GitHub.connectAnonymously();
GHRelease latestRelease = gitHub.getRepository("CASELOAD7000/knockback-sync")
GHRelease latestRelease = gitHub.getRepository("Axionize/knockback-sync")
.getLatestRelease();
List<GHAsset> assets = latestRelease.listAssets().toList();
for (GHAsset asset : assets) {
@@ -64,12 +68,9 @@ public class BuildTypePie extends SimplePie {
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()))) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
try (InputStream inputStream = new URL(downloadUrl).openStream();
FileOutputStream outputStream = new FileOutputStream(new File(dataFolder, asset.getName()))) {
inputStream.transferTo(outputStream);
}
KnockbackSyncBase.INSTANCE.getLogger().info("Downloaded: " + asset.getName());
@@ -87,13 +88,7 @@ public class BuildTypePie extends SimplePie {
private static String readStringFromURL(String urlString) throws IOException {
try (InputStream inputStream = new URL(urlString).openStream()) {
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
result.write(buffer, 0, bytesRead);
}
return result.toString("UTF-8");
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
}
}
}
@@ -1,5 +1,5 @@
package me.caseload.knockbacksync.stats.custom;
public interface Metrics {
void shutdown();
public void shutdown();
}
@@ -2,7 +2,6 @@ package me.caseload.knockbacksync.stats.custom;
import me.caseload.knockbacksync.stats.CustomChart;
import me.caseload.knockbacksync.stats.JsonObjectBuilder;
import org.jetbrains.annotations.NotNull;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
@@ -95,7 +94,20 @@ public class MetricsBase {
boolean logSentData,
boolean logResponseStatusText,
boolean skipRelocateCheck) {
this.scheduler = getScheduledThreadPoolExecutor();
ScheduledThreadPoolExecutor scheduler =
new ScheduledThreadPoolExecutor(
1,
task -> {
Thread thread = new Thread(task, "bStats-Metrics");
thread.setDaemon(true);
return thread;
});
// We want delayed tasks (non-periodic) that will execute in the future to be
// cancelled when the scheduler is shutdown.
// Otherwise, we risk preventing the server from shutting down even when
// MetricsBase#shutdown() is called
scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
this.scheduler = scheduler;
this.platform = platform;
this.serverUuid = serverUuid;
this.serviceId = serviceId;
@@ -119,23 +131,6 @@ public class MetricsBase {
}
}
private static @NotNull ScheduledThreadPoolExecutor getScheduledThreadPoolExecutor() {
ScheduledThreadPoolExecutor scheduler =
new ScheduledThreadPoolExecutor(
1,
task -> {
Thread thread = new Thread(task, "bStats-Metrics");
thread.setDaemon(true);
return thread;
});
// We want delayed tasks (non-periodic) that will execute in the future to be
// cancelled when the scheduler is shutdown.
// Otherwise, we risk preventing the server from shutting down even when
// MetricsBase#shutdown() is called
scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
return scheduler;
}
/**
* Gzips the given string.
*
@@ -6,7 +6,7 @@ import java.security.MessageDigest;
public class PluginJarHashProvider {
protected final URL jarURL;
protected URL jarURL;
public PluginJarHashProvider(URL jarURL) {
this.jarURL = jarURL;
@@ -1,12 +1,13 @@
package me.caseload.knockbacksync.stats.custom;
import lombok.Getter;
@Getter
public abstract class StatsManager {
Metrics metrics;
public Metrics getMetrics() {
return metrics;
}
public void init() {
throw new IllegalStateException("Empty stats class!");
}
@@ -9,7 +9,8 @@ public class NumberConversions {
} else {
try {
return Long.parseLong(object.toString());
} catch (NumberFormatException | NullPointerException ignored) {
} catch (NumberFormatException var2) {
} catch (NullPointerException var3) {
}
return defaultValue;
@@ -22,7 +23,8 @@ public class NumberConversions {
} else {
try {
return Integer.parseInt(object.toString());
} catch (NumberFormatException | NullPointerException ignored) {
} catch (NumberFormatException var2) {
} catch (NullPointerException var3) {
}
return def;
@@ -1,10 +1,7 @@
package me.caseload.knockbacksync.util.data;
import lombok.Getter;
import java.util.Objects;
@Getter
public class Pair<A, B> {
private final A first;
private final B second;
@@ -18,6 +15,14 @@ public class Pair<A, B> {
return new Pair<T, K>(a, b);
}
public A getFirst() {
return first;
}
public B getSecond() {
return second;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Pair)) {
+4 -17
View File
@@ -2,9 +2,6 @@
# KnockbackSync Configuration #
#########################################
# Do not modify!
config-version: 2
# Plugin enabled state
# Toggleable using /knockbacksync toggle
# Required permission: knockbacksync.toggle
@@ -18,7 +15,7 @@ notify_updates: true
# Disabling this may lead to inaccuracies in calculations
runnable:
enabled: true # Runnable enabled state
interval: 20 # The interval in ticks between sending out pings to players
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.
@@ -26,19 +23,6 @@ runnable:
# the threshold, the previous ping value is used to avoid calculation inaccuracies.
spike_threshold: 20
# OPTIONS: KEEPALIVE, PING, TRANSACTION - PING and TRANSACTION are aliases for each other and are effectively the same
# The packet type used to measure ping by the runnable.
# KEEPALIVE is the most accurate however sending too many of them too often (such as using a runnable with a low interval)
# - May disconnect players, especially laggy ones if sent too often
# - The default interval of 20 ticks or 1 second
# means it is in theory possible for a player to have a ping spike in-between two sword attacks
# and have it immediately spike back down, slightly throwing off knockback synchronization
# in practice this is very rare and the extra ping accuracy and precision is more valued
# PING/TRANSACTION can be sent more often (and therefore better detect ping spikes) but are less precise
# ping measured in ms with PING/TRANSACTION is typically within ± 5ms of actual but can be up to 50ms off
ping_strategy: "KEEPALIVE"
# This setting has yet to be implemented. Enable if you want it to be enabled as soon as the next update add support
enable_experimental_offground: false
enable_message: "&aSuccessfully enabled KnockbackSync."
@@ -52,3 +36,6 @@ player_status_message: "&e%player%'s KnockbackSync status: "
player_disabled_global_message: "&cDisabled (Global toggle is off)"
enable_experimental_offground_message: "&aSuccessfully enabled offground experiment."
disable_experimental_offground_message: "&cSuccessfully disabled offground experiment."
# Read FAQ!
ping_offset: 25
+33 -1
View File
@@ -12,6 +12,17 @@ val shadeThisThing: Configuration by configurations.creating {
isTransitive = true
}
//sourceSets {
// main {
// java {
// srcDir(project(":common").sourceSets.main.get().java.srcDirs)
// }
// resources {
// srcDir(project(":common").sourceSets.main.get().resources.srcDirs)
// }
// }
//}
// TODO migrate to only including sourceset for compile, test and javadoc tasks
// Currently must build with gradle build -x test to skip test
tasks.withType<JavaCompile>().configureEach {
@@ -26,6 +37,19 @@ tasks.named<JavaCompile>("compileTestJava") {
exclude("**/*")
}
//tasks.withType<Test>().configureEach {
// classpath += project(":common").sourceSets["main"].output + configurations["testRuntimeClasspath"]
//}
//tasks.named<Jar>("sourcesJar") {
// from(project(":common").sourceSets.main.get().allSource)
//}
tasks.jar {
// from(project(":common").sourceSets.named("main").get().output)
// from(project(":common").sourceSets.main.get().resources)
}
dependencies {
implementation(project(":common"))
@@ -39,12 +63,20 @@ dependencies {
include(modImplementation("me.lucko:fabric-permissions-api:0.3.1")!!)
include(modImplementation("com.github.retrooper:packetevents-fabric:2.5.8-SNAPSHOT")!!)
include(modImplementation("org.incendo:cloud-fabric:2.0.0-beta.9")!!)
shadeThisThing(implementation("com.fasterxml.jackson.core:jackson-databind:2.17.2")!!)
shadeThisThing(implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.2")!!)
shadeThisThing(implementation("org.kohsuke:github-api:1.326")!!)
// include(implementation("com.fasterxml.jackson.core:jackson-databind:2.17.2")!!)
// include(implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.2")!!)
// include(implementation("org.kohsuke:github-api:1.326")!!)
// include(implementation("org.kohsuke:github-api:1.326") {
// exclude(group = "commons-io", module = "commons-io")
// exclude(group = "org.apache.commons", module = "commons-lang3")
// })
compileOnly("org.geysermc.floodgate:api:2.0-SNAPSHOT")
compileOnly("org.projectlombok:lombok:1.18.34")
annotationProcessor("org.projectlombok:lombok:1.18.34")
@@ -1,5 +1,6 @@
package me.caseload.knockbacksync;
import me.caseload.knockbacksync.world.KBSyncFabricBase;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.loader.api.FabricLoader;
@@ -8,6 +9,8 @@ import net.minecraft.server.MinecraftServer;
public class KBSyncFabricLoaderMod implements PreLaunchEntrypoint, ModInitializer {
// private static MinecraftServer SERVER;
private final KnockbackSyncBase core = new KBSyncFabricBase();
public static MinecraftServer getServer() {
@@ -17,6 +20,13 @@ public class KBSyncFabricLoaderMod implements PreLaunchEntrypoint, ModInitialize
@Override
public void onPreLaunch() {
core.load();
// ServerLifecycleEvents.SERVER_STARTING.register((minecraftServer -> {
// SERVER = minecraftServer;
// core.initializeScheduler();
// core.configManager.loadConfig(false);
// core.statsManager.init();
// core.checkForUpdates();
// }));
}
@Override
@@ -30,5 +40,8 @@ public class KBSyncFabricLoaderMod implements PreLaunchEntrypoint, ModInitialize
core.scheduler.shutdown();
core.statsManager.getMetrics().shutdown();
});
// SenderFactory<KnockbackSyncBase, FabricCommandSource> fabricSenderFactory = new FabricSenderFactory(null);
// LiteralArgumentBuilder<FabricCommandSource> fabricCommand = ReloadSubCommand.build(fabricSenderFactory);
}
}
@@ -1,19 +1,17 @@
package me.caseload.knockbacksync.sender;
package me.caseload.knockbacksync.command;
import me.caseload.knockbacksync.KBSyncFabricBase;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.mixin.CommandStackSourceAccessor;
import me.caseload.knockbacksync.permission.FabricPermissionChecker;
import me.caseload.knockbacksync.world.KBSyncFabricBase;
import net.minecraft.commands.CommandSource;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.rcon.RconConsoleSource;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.SenderMapper;
import java.util.UUID;
public class FabricSenderFactory extends SenderFactory<KBSyncFabricBase, CommandSourceStack> implements SenderMapper<CommandSourceStack, Sender> {
public class FabricSenderFactory extends SenderFactory<KBSyncFabricBase, CommandSourceStack> {
private final KBSyncFabricBase plugin;
public FabricSenderFactory(KBSyncFabricBase kbSyncFabricBase) {
@@ -31,14 +29,14 @@ public class FabricSenderFactory extends SenderFactory<KBSyncFabricBase, Command
if (commandSource.getEntity() != null) {
return commandSource.getEntity().getUUID();
}
return Sender.CONSOLE_UUID;
return PlatformSender.CONSOLE_UUID;
}
@Override
protected String getName(CommandSourceStack commandSource) {
String name = commandSource.getTextName();
if (commandSource.getEntity() != null && name.equals("Server")) {
return Sender.CONSOLE_NAME;
return PlatformSender.CONSOLE_NAME;
}
return name;
}
@@ -61,11 +59,6 @@ public class FabricSenderFactory extends SenderFactory<KBSyncFabricBase, Command
return ((FabricPermissionChecker) KnockbackSyncBase.INSTANCE.getPermissionChecker()).hasPermission(commandSource, node);
}
@Override
protected boolean hasPermission(CommandSourceStack commandSource, String node, boolean defaultIfUnset) {
return ((FabricPermissionChecker) KnockbackSyncBase.INSTANCE.getPermissionChecker()).hasPermission(commandSource, node, defaultIfUnset);
}
@Override
protected void performCommand(CommandSourceStack sender, String command) {
// sender.getServer().getCommandManager().executeWithPrefix(sender, command);
@@ -79,21 +72,6 @@ public class FabricSenderFactory extends SenderFactory<KBSyncFabricBase, Command
(output == CommandSource.NULL && sender.getTextName().equals("")); // Functions
}
@Override
public @NonNull Sender map(@NonNull CommandSourceStack base) {
return this.wrap(base);
}
@Override
public @NonNull CommandSourceStack reverse(@NonNull Sender mapped) {
return this.unwrap(mapped);
}
@Override
public void close() throws Exception {
}
// public static Text toNativeText(Component component) {
// return Text.Serialization.fromJsonTree(GsonComponentSerializer.gson().serializeToTree(component), DynamicRegistryManager.EMPTY);
// }
@@ -0,0 +1,33 @@
package me.caseload.knockbacksync.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import me.caseload.knockbacksync.command.subcommand.*;
import me.caseload.knockbacksync.util.ChatUtil;
import me.caseload.knockbacksync.util.CommandUtil;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
public class KnockbackSyncCommand implements Command<CommandSourceStack> {
public static LiteralArgumentBuilder<CommandSourceStack> build() {
return Commands.literal("knockbacksync")
.then(PingSubCommand.build())
.then(ReloadSubCommand.build())
.then(StatusSubCommand.build())
.then(ToggleSubCommand.build())
.then(ToggleOffGroundSubcommand.build())
.executes(new KnockbackSyncCommand());
}
@Override
public int run(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
CommandUtil.sendSuccessMessage(context, ChatUtil.translateAlternateColorCodes(
'&',
"&6This server is running the &eKnockbackSync &6plugin. &bhttps://github.com/CASELOAD7000/knockback-sync"
));
return 1;
}
}
@@ -0,0 +1,44 @@
package me.caseload.knockbacksync.command.subcommand;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.command.PlatformSender;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.player.PlayerData;
import me.caseload.knockbacksync.util.ChatUtil;
import me.caseload.knockbacksync.util.CommandUtil;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.commands.arguments.selector.EntitySelector;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import static me.caseload.knockbacksync.util.ChatUtil.getPingMessage;
public class PingSubCommand {
public static LiteralArgumentBuilder<CommandSourceStack> build() {
return Commands.literal("ping")
.requires(source -> true)
.executes(context -> { // Added .executes() here to handle no target
if (context.getSource().getEntity() instanceof ServerPlayer sender) {
CommandUtil.sendSuccessMessage(context, () -> Component.literal(getPingMessage(sender.getUUID(), null)));
} else {
CommandUtil.sendFailureMessage(context, "You must specify a player to use this command from the console.");
}
return 1;
})
.then(Commands.argument("target", EntityArgument.player())
.executes(context -> {
EntitySelector selector = context.getArgument("target", EntitySelector.class);
ServerPlayer target = selector.findSinglePlayer(context.getSource());
if (context.getSource().getEntity() instanceof ServerPlayer sender) {
CommandUtil.sendSuccessMessage(context, getPingMessage(sender.getUUID(), target.getUUID()));
} else {
CommandUtil.sendSuccessMessage(context, getPingMessage(PlatformSender.CONSOLE_UUID, target.getUUID()));
}
return 1;
})
);
}
}
@@ -0,0 +1,29 @@
package me.caseload.knockbacksync.command.subcommand;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.manager.ConfigManager;
import me.caseload.knockbacksync.util.ChatUtil;
import me.caseload.knockbacksync.util.CommandUtil;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
public class ReloadSubCommand {
public static LiteralArgumentBuilder<CommandSourceStack> build() {
return Commands.literal("reload")
.requires(source -> KnockbackSyncBase.INSTANCE.getPermissionChecker().hasPermission(source, "knockbacksync.reload", false))
.executes(context -> {
ConfigManager configManager = KnockbackSyncBase.INSTANCE.getConfigManager();
configManager.loadConfig(true);
String rawReloadMessage = configManager.getReloadMessage();
String reloadMessage = ChatUtil.translateAlternateColorCodes('&', rawReloadMessage);
// Send the message to the command source (the player or console that executed the command)
CommandUtil.sendSuccessMessage(context, reloadMessage);
return Command.SINGLE_SUCCESS;
});
}
}
@@ -0,0 +1,81 @@
package me.caseload.knockbacksync.command.subcommand;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.manager.ConfigManager;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.permission.PermissionChecker;
import me.caseload.knockbacksync.util.ChatUtil;
import me.caseload.knockbacksync.util.CommandUtil;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.commands.arguments.selector.EntitySelector;
import net.minecraft.server.level.ServerPlayer;
import java.util.UUID;
public class StatusSubCommand implements Command<CommandSourceStack> {
private static final PermissionChecker permissionChecker = KnockbackSyncBase.INSTANCE.getPermissionChecker();
public static LiteralArgumentBuilder<CommandSourceStack> build() {
return Commands.literal("status")
.requires(source -> permissionChecker.hasPermission(source, "knockbacksync.status.self", true)) // Requires at least self permission
.executes(new StatusSubCommand()) // Execute for self status
.then(Commands.argument("target", EntityArgument.player())
.requires(source -> permissionChecker.hasPermission(source, "knockbacksync.status.other", true)) // Requires other permission for target
.executes(context -> {
ConfigManager configManager = KnockbackSyncBase.INSTANCE.getConfigManager();
EntitySelector selector = context.getArgument("target", EntitySelector.class);
ServerPlayer target = selector.findSinglePlayer(context.getSource());
ServerPlayer sender = context.getSource().getPlayer();
showPlayerStatus(context, null, target, configManager);
return Command.SINGLE_SUCCESS;
}));
}
private static void showPlayerStatus(CommandContext<CommandSourceStack> context, ServerPlayer sender, ServerPlayer target, ConfigManager configManager) {
boolean globalStatus = configManager.isToggled();
UUID uuid = target.getUUID();
boolean playerStatus = PlayerDataManager.containsPlayerData(uuid);
if (!globalStatus) {
CommandUtil.sendSuccessMessage(context, ChatUtil.translateAlternateColorCodes('&',
configManager.getConfigWrapper().getString("player_status_message", "&e%player%'s KnockbackSync status: ")
.replace("%player%", target.getDisplayName().getString())) +
ChatUtil.translateAlternateColorCodes('&',
configManager.getConfigWrapper().getString("player_disabled_global_message", "&cDisabled (Global toggle is off)")));
} else {
CommandUtil.sendSuccessMessage(context, ChatUtil.translateAlternateColorCodes('&',
configManager.getConfigWrapper().getString("player_status_message", "&e%player%'s KnockbackSync status: ")
.replace("%player%", target.getDisplayName().getString())) +
(playerStatus
? ChatUtil.translateAlternateColorCodes('&', "&aEnabled")
: ChatUtil.translateAlternateColorCodes('&', "&cDisabled")));
}
}
@Override
public int run(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
ConfigManager configManager = KnockbackSyncBase.INSTANCE.getConfigManager();
// Show global status
boolean globalStatus = configManager.isToggled();
CommandUtil.sendSuccessMessage(context, ChatUtil.translateAlternateColorCodes('&',
configManager.getConfigWrapper().getString("global_status_message", "&eGlobal KnockbackSync status: ")) +
(globalStatus
? ChatUtil.translateAlternateColorCodes('&', "&aEnabled")
: ChatUtil.translateAlternateColorCodes('&', "&cDisabled")));
// Show player status for the sender (no target specified)
if (context.getSource().getEntity() instanceof ServerPlayer sender) {
showPlayerStatus(context, sender, sender, configManager);
}
return Command.SINGLE_SUCCESS;
}
}
@@ -0,0 +1,36 @@
package me.caseload.knockbacksync.command.subcommand;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.permission.PermissionChecker;
import me.caseload.knockbacksync.util.ChatUtil;
import me.caseload.knockbacksync.util.CommandUtil;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
public class ToggleOffGroundSubcommand implements Command<CommandSourceStack> {
private static final PermissionChecker permissionChecker = KnockbackSyncBase.INSTANCE.getPermissionChecker();
public static LiteralArgumentBuilder<CommandSourceStack> build() {
return Commands.literal("toggleoffground")
.requires(source -> permissionChecker.hasPermission(source, "knockbacksync.toggleoffground", false))
.executes(new ToggleOffGroundSubcommand());
}
@Override
public int run(CommandContext<CommandSourceStack> context) {
boolean toggledState = !KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper().getBoolean("enable_experimental_offground", false);
KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper().set("enable_experimental_offground", toggledState);
KnockbackSyncBase.INSTANCE.getConfigManager().saveConfig();
String message = ChatUtil.translateAlternateColorCodes('&',
toggledState ?
KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper().getString("enable_experimental_offground_message", "&aSuccessfully enabled offground experiment.") :
KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper().getString("disable_experimental_offground_message", "&cSuccessfully disabled offground experiment.")
);
CommandUtil.sendSuccessMessage(context, message);
return Command.SINGLE_SUCCESS;
}
}
@@ -0,0 +1,118 @@
package me.caseload.knockbacksync.command.subcommand;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.manager.ConfigManager;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.permission.PermissionChecker;
import me.caseload.knockbacksync.player.PlayerData;
import me.caseload.knockbacksync.util.ChatUtil;
import me.caseload.knockbacksync.util.CommandUtil;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.commands.arguments.selector.EntitySelector;
import net.minecraft.server.level.ServerPlayer;
import java.util.UUID;
import static me.caseload.knockbacksync.util.CommandUtil.sendFailureMessage;
import static me.caseload.knockbacksync.util.CommandUtil.sendSuccessMessage;
public class ToggleSubCommand implements Command<CommandSourceStack> {
private static final PermissionChecker permissionChecker = KnockbackSyncBase.INSTANCE.getPermissionChecker();
public static LiteralArgumentBuilder<CommandSourceStack> build() {
return Commands.literal("toggle")
.requires(source -> permissionChecker.hasPermission(source, "knockbacksync.toggle.self", true)) // Requires at least self permission
.executes(new ToggleSubCommand()) // Execute for self toggle
.then(Commands.argument("target", EntityArgument.player())
// .requires(source -> permissionChecker.hasPermission(source, "knockbacksync.toggle.other", true)) // Requires other permission for target
.executes(context -> {
ConfigManager configManager = KnockbackSyncBase.INSTANCE.getConfigManager();
EntitySelector selector = context.getArgument("target", EntitySelector.class);
ServerPlayer target = selector.findSinglePlayer(context.getSource());
ServerPlayer sender = context.getSource().getPlayerOrException();
if (!target.equals(sender) && permissionChecker.hasPermission(context.getSource(), "knockbacksync.toggle.self", true)) {
CommandUtil.sendFailureMessage(context, "You do not have permission to toggle the knockback of other player's.");
return 0;
}
if (!configManager.isToggled()) {
sendSuccessMessage(context, ChatUtil.translateAlternateColorCodes('&', "&cKnockbacksync is currently disabled on this server. Contact your server administrator for more information."));
} else {
togglePlayerKnockback(target, configManager, context);
}
return Command.SINGLE_SUCCESS;
}));
}
private static void toggleGlobalKnockback(ConfigManager configManager, CommandContext<CommandSourceStack> context) {
boolean toggledState = !configManager.isToggled();
configManager.setToggled(toggledState);
KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper().set("enabled", toggledState);
KnockbackSyncBase.INSTANCE.getConfigManager().saveConfig();
String message = ChatUtil.translateAlternateColorCodes('&',
toggledState ? configManager.getEnableMessage() : configManager.getDisableMessage()
);
sendSuccessMessage(context, message);
}
private static void togglePlayerKnockback(ServerPlayer target, ConfigManager configManager, CommandContext<CommandSourceStack> context) {
UUID uuid = target.getUUID();
if (PlayerDataManager.shouldExempt(uuid)) {
String message = ChatUtil.translateAlternateColorCodes('&',
configManager.getPlayerIneligibleMessage()
).replace("%player%", target.getDisplayName().getString());
sendSuccessMessage(context, message);
return;
}
boolean hasPlayerData = PlayerDataManager.containsPlayerData(uuid);
if (hasPlayerData)
PlayerDataManager.removePlayerData(uuid);
else {
PlayerDataManager.addPlayerData(uuid, new PlayerData(KnockbackSyncBase.INSTANCE.platformServer.getPlayer(uuid)));
// switch (KnockbackSyncBase.INSTANCE.platform) {
// case BUKKIT:
// case FOLIA:
// org.bukkit.entity.Player player = org.bukkit.Bukkit.getPlayer(target.getUUID());
// PlayerDataManager.addPlayerData(uuid, new PlayerData(target.getUUID());
// case FABRIC:
// PlayerDataManager.addPlayerData(uuid, new PlayerData(target));
// }
}
String message = ChatUtil.translateAlternateColorCodes('&',
hasPlayerData ? configManager.getPlayerDisableMessage() : configManager.
getPlayerEnableMessage()
).replace("%player%", target.getDisplayName().getString());
sendSuccessMessage(context, message);
}
@Override
public int run(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
ConfigManager configManager = KnockbackSyncBase.INSTANCE.getConfigManager();
ServerPlayer sender = context.getSource().getPlayerOrException();
// Global toggle
if (permissionChecker.hasPermission(context.getSource(), "knockbacksync.toggle.global", false)) {
toggleGlobalKnockback(configManager, context);
} else {
sendSuccessMessage(context, ChatUtil.translateAlternateColorCodes('&', "&cYou don't have permission to toggle the global setting."));
}
return Command.SINGLE_SUCCESS;
}
}
@@ -21,7 +21,7 @@ public class ServerEntityMixin {
@Final
private Entity entity;
@Inject(method = "sendChanges", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/Entity;hurtMarked:Z", ordinal = 1), cancellable = true)
@Inject(method = "sendChanges", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/Entity;hurtMarked:Z", ordinal = 1))
private void onSendChanges(CallbackInfo ci) {
if (this.entity.hurtMarked && this.entity instanceof ServerPlayer player) {
Vec3 velocity = player.getDeltaMovement();
@@ -1,32 +1,28 @@
package me.caseload.knockbacksync.permission;
import me.caseload.knockbacksync.command.PlatformSender;
import me.caseload.knockbacksync.player.FabricPlayer;
import me.caseload.knockbacksync.player.PlatformPlayer;
import me.caseload.knockbacksync.sender.Sender;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.commands.CommandSourceStack;
public class FabricPermissionChecker implements PermissionChecker {
public boolean hasPermission(CommandSourceStack source, String permission) {
return Permissions.check(source, permission, false);
}
public boolean hasPermission(CommandSourceStack source, String permission, boolean defaultIfUnset) {
return Permissions.check(source, permission, defaultIfUnset);
return Permissions.check(source, permission);
}
@Override
public boolean hasPermission(Object nativeType, String s, boolean defaultIfUnset) {
if (nativeType instanceof CommandSourceStack commandSourceStack) {
public boolean hasPermission(Object object, String s, boolean defaultIfUnset) {
if (object instanceof CommandSourceStack commandSourceStack) {
return Permissions.check(commandSourceStack, s, defaultIfUnset);
}
throw new IllegalArgumentException();
}
@Override
public boolean hasPermission(Sender source, String s, boolean defaultIfUnset) {
return source.hasPermission(s);
public boolean hasPermission(PlatformSender source, String s, boolean defaultIfUnset) {
return false;
}
@Override
@@ -1,36 +0,0 @@
package me.caseload.knockbacksync.sender;
import me.caseload.knockbacksync.command.generic.PlayerSelector;
import me.caseload.knockbacksync.player.FabricPlayer;
import me.caseload.knockbacksync.player.PlatformPlayer;
import java.util.Collection;
import java.util.Collections;
public class FabricPlayerSelectorAdapter implements PlayerSelector {
private final org.incendo.cloud.minecraft.modded.data.SinglePlayerSelector fabricSelector;
public FabricPlayerSelectorAdapter(org.incendo.cloud.minecraft.modded.data.SinglePlayerSelector fabricSelector) {
this.fabricSelector = fabricSelector;
}
@Override
public boolean isSingle() {
return true;
}
@Override
public PlatformPlayer getSinglePlayer() {
return new FabricPlayer(fabricSelector.single()); // Assuming your ServerPlayer can be cast to Player
}
@Override
public Collection<PlatformPlayer> getPlayers() {
return Collections.singletonList(new FabricPlayer(fabricSelector.single())); // Assuming your ServerPlayer can be cast to Player
}
@Override
public String inputString() {
return fabricSelector.inputString();
}
}
@@ -1,27 +0,0 @@
package me.caseload.knockbacksync.sender;
import me.caseload.knockbacksync.command.generic.AbstractPlayerSelectorParser;
import me.caseload.knockbacksync.command.generic.PlayerSelector;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.minecraft.modded.parser.VanillaArgumentParsers;
import org.incendo.cloud.parser.ParserDescriptor;
import java.util.concurrent.CompletableFuture;
public class FabricPlayerSelectorParser<C> extends AbstractPlayerSelectorParser<C> {
@Override
public ParserDescriptor<C, PlayerSelector> descriptor() {
return createDescriptor();
}
@Override
protected ParserDescriptor<C, ?> getPlatformSpecificDescriptor() {
return VanillaArgumentParsers.singlePlayerSelectorParser();
}
@Override
protected CompletableFuture<PlayerSelector> adaptToCommonSelector(CommandContext<C> context, Object platformSpecificSelector) {
return CompletableFuture.completedFuture(new FabricPlayerSelectorAdapter((org.incendo.cloud.minecraft.modded.data.SinglePlayerSelector) platformSpecificSelector));
}
}
@@ -1,25 +1,24 @@
package me.caseload.knockbacksync;
package me.caseload.knockbacksync.world;
import com.github.retrooper.packetevents.PacketEvents;
import io.github.retrooper.packetevents.factory.fabric.FabricPacketEventsBuilder;
import lombok.Getter;
import me.caseload.knockbacksync.KBSyncFabricLoaderMod;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.command.FabricSenderFactory;
import me.caseload.knockbacksync.command.KnockbackSyncCommand;
import me.caseload.knockbacksync.listener.fabric.FabricPlayerDamageListener;
import me.caseload.knockbacksync.listener.fabric.FabricPlayerJoinQuitListener;
import me.caseload.knockbacksync.listener.fabric.FabricPlayerKnockbackListener;
import me.caseload.knockbacksync.listener.fabric.FabricTickRateChangeListener;
import me.caseload.knockbacksync.manager.ConfigManager;
import me.caseload.knockbacksync.permission.FabricPermissionChecker;
import me.caseload.knockbacksync.permission.PermissionChecker;
import me.caseload.knockbacksync.scheduler.FabricSchedulerAdapter;
import me.caseload.knockbacksync.sender.FabricPlayerSelectorParser;
import me.caseload.knockbacksync.sender.FabricSenderFactory;
import me.caseload.knockbacksync.stats.custom.FabricStatsManager;
import me.caseload.knockbacksync.stats.custom.PluginJarHashProvider;
import me.caseload.knockbacksync.world.FabricServer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.fabric.FabricServerCommandManager;
import net.minecraft.commands.Commands;
import java.io.File;
import java.io.IOException;
@@ -34,19 +33,23 @@ public class KBSyncFabricBase extends KnockbackSyncBase {
private final Logger logger = Logger.getLogger(KBSyncFabricLoaderMod.class.getName());
private final FabricPermissionChecker permissionChecker = new FabricPermissionChecker();
@Getter
private final FabricSenderFactory fabricSenderFactory = new FabricSenderFactory(this);
public KBSyncFabricBase() {
super.configManager = new ConfigManager();
super.playerSelectorParser = new FabricPlayerSelectorParser<>();
super.commandManager = new FabricServerCommandManager<>(
ExecutionCoordinator.simpleCoordinator(),
fabricSenderFactory
);
super.statsManager = new FabricStatsManager();
super.platformServer = new FabricServer();
super.pluginJarHashProvider = new PluginJarHashProvider(getJarURL());
statsManager = new FabricStatsManager();
platformServer = new FabricServer();
URL jarUrl = null;
Optional<ModContainer> modContainer = FabricLoader.getInstance().getModContainer("knockbacksync");
if (modContainer.isPresent()) {
String jarPath = modContainer.get().getRootPath().getFileSystem().toString();
jarPath = jarPath.replaceAll("^jar:", "").replaceAll("!/$", "");
try {
jarUrl = new File(jarPath).toURI().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
pluginJarHashProvider = new PluginJarHashProvider(jarUrl);
super.setSenderFactory(new FabricSenderFactory(this));
}
@Override
@@ -83,6 +86,20 @@ public class KBSyncFabricBase extends KnockbackSyncBase {
new FabricTickRateChangeListener().register();
}
@Override
protected void registerCommands() {
// ServerLifecycleEvents.SERVER_STARTED.register(server -> {
// CommandDispatcher<CommandSourceStack> dispatcher = server.getCommands().getDispatcher();
// dispatcher.register(KnockbackSyncCommand.build());
// });
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
dispatcher.register(KnockbackSyncCommand.build());
dispatcher.register(
Commands.literal("kbsync")
.redirect(dispatcher.getRoot().getChild("knockbacksync")));
});
}
@Override
protected String getVersion() {
return FabricLoader.getInstance().getModContainer("knockbacksync")
@@ -109,18 +126,12 @@ public class KBSyncFabricBase extends KnockbackSyncBase {
return permissionChecker;
}
private URL getJarURL() {
URL jarUrl = null;
Optional<ModContainer> modContainer = FabricLoader.getInstance().getModContainer("knockbacksync");
if (modContainer.isPresent()) {
String jarPath = modContainer.get().getRootPath().getFileSystem().toString();
jarPath = jarPath.replaceAll("^jar:", "").replaceAll("!/$", "");
try {
jarUrl = new File(jarPath).toURI().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
return jarUrl;
@Override
public void initializePacketEvents() {
PacketEvents.getAPI().getSettings()
.checkForUpdates(false)
.debug(false);
PacketEvents.getAPI().init();
}
}