74 Commits
Author SHA1 Message Date
CASELOAD7000 47ff08d62a Revert to getHitBlockPosition() 2024-10-26 16:09:08 -05:00
CASELOAD7000 185bb72f10 Reduce redundancy and fix block check 2024-10-26 15:21:10 -05:00
Axionize 74ee5d8c7e Initial offground synchronization commit 2024-10-26 14:40:40 -04:00
Axionize 2d98d4cbff Compensated ping message formatting 2024-10-26 14:40:38 -04:00
Axionize c2bacd2f5c Improve accuracy of getDistanceToGround() 2024-10-26 12:47:04 -04:00
Axionize 164d073861 Merge branch 'dev-upstream' into dev 2024-10-26 05:08:54 -04:00
Axionize 77c4dde70f Merge branch 'workspace' into dev-upstream 2024-10-26 05:08:36 -04:00
Axionize 3745648821 Fix config reloading 2024-10-26 05:07:53 -04:00
Axionize b339a53dc8 Fix config reloading 2024-10-26 04:39:35 -04:00
Axionize 3895b35941 Merge branch 'dev-upstream' into dev 2024-10-26 04:32:18 -04:00
Axionize ac5175b63c Decrease keepalive interval and autoupdate config (#27) 2024-10-26 04:31:45 -04:00
Axionize 8e61e44012 Merge branch 'dev-upstream' into dev 2024-10-26 03:46:05 -04:00
Axionize 233a1e04d3 Fix showing wrong ping and jitter after toggling knockbacksync off and on (#26) 2024-10-26 03:44:34 -04:00
Axionize 170caff375 fix showing wrong pinger and jitter after toggling off and then on 2024-10-26 03:41:29 -04:00
Axionize a2cc42a0a7 Filter out null trackedEntitiy 2024-10-26 03:03:27 -04:00
Axionize ae76933a5d PacketEvents, Tick Rate, and Ping Command Info Changes
- Account for /tick rate properly on Fabric once again, can be modified in the future to account for /tick rate on bukkit
- Switch back to using my PacketEvents fork for Fabric
- Add more information to the ping command including if ping has spiked or not
2024-10-26 02:57:05 -04:00
CASELOAD7000 992ab4002a Switch PacketEvents build 2024-10-26 01:09:00 -05:00
CASELOAD7000 bb5b39810c Account for /tick rate 2024-10-26 01:07:01 -05:00
CASELOAD7000 dc7edf4361 Add more information to ping command 2024-10-26 01:01:30 -05:00
Axionize 702618482d Minor code cleanup, use only Java 8 features 2024-10-26 01:02:51 -04:00
Axionize a35b208df2 Merge branch 'dev' into dev-upstream
# Conflicts:
#	bukkit/src/main/java/me/caseload/knockbacksync/BukkitBase.java
2024-10-26 00:43:46 -04:00
Axionize a51e9ac32e Fix player death causing console spam with new updateInterval() 2024-10-26 00:39:57 -04:00
Axionize f4df09802b Delete unused UpdateIntervalListener 2024-10-26 00:21:07 -04:00
CASELOAD7000 0a08b76d35 Change default permissions 2024-10-25 23:14:11 -05:00
CASELOAD7000 f7e572ef02 Rename getJumpVelocity to getJumpPower 2024-10-25 21:30:30 -05:00
CASELOAD7000 40d60b62a2 Improve prediction accuracy 2024-10-25 20:18:04 -05:00
CASELOAD7000 92f0cf7729 Improve prediction accuracy 2024-10-25 20:10:57 -05:00
CASELOAD7000 b34417d1c8 Remove PlayerJumpListener 2024-10-25 19:21:23 -05:00
Axionize b9cc9f1f7f Merge branch 'dev' into dev-upstream 2024-10-25 16:47:31 -04:00
Axionize 0c7b6effde Add support for custom updateInterval() for players on Bukkit/Spigot/Paper + Folia > 1.20.5 2024-10-25 16:47:11 -04:00
Axionize f2807153b5 Fix README 2024-10-25 15:00:50 -04:00
Axionize 4a460f28cd Update developer notes 2024-10-25 14:59:23 -04:00
Axionize fb62b8d99d Remove maven build file 2024-10-25 14:55:25 -04:00
Axionize e056cec07c Custom events and reloading now fully work 2024-10-25 14:18:12 -04:00
Axionize e18a58e4f2 Make config edits from in-game preserve comments 2024-10-25 13:37:55 -04:00
Axionize 36a2bb5165 Fabric crash hotfix 2024-10-25 10:05:06 -04:00
Axionize 7a898acfd9 Update buildscript to use repo variables 2024-10-25 09:17:15 -04:00
Axionize ac5d3307ed Gradle cache builds 2024-10-24 21:40:58 -04:00
Axionize a652147e2c Make buildscript use variables for Modrinth ID and repository URL; Make hash provider gitihub URL build-time configurable 2024-10-24 21:38:02 -04:00
Axionize f7c4e179b7 Fix plugin.yml 2024-10-24 21:15:03 -04:00
Axionize 3991e8f09a Add missing mixin on Fabric 2024-10-24 21:02:33 -04:00
Axionize 44239355b9 Fabric build fix 2024-10-24 20:58:18 -04:00
Axionize 0b6af2aacd Minor refactor 2024-10-24 19:44:20 -04:00
Axionize 85c6a2cf14 Implement updateInterval() modification and getJumpVelocity() on Fabric 2024-10-24 19:43:53 -04:00
Axionize ca937a86d0 Merge branch 'dev' into dev-upstream
# Conflicts:
#	bukkit/src/main/java/me/caseload/knockbacksync/player/BukkitPlayer.java
#	common/src/main/java/me/caseload/knockbacksync/command/subcommand/PingCommand.java
#	common/src/main/java/me/caseload/knockbacksync/command/subcommand/ReloadCommand.java
#	common/src/main/java/me/caseload/knockbacksync/command/subcommand/StatusCommand.java
#	common/src/main/java/me/caseload/knockbacksync/player/PlatformPlayer.java
#	fabric/src/main/java/me/caseload/knockbacksync/player/FabricPlayer.java
2024-10-24 17:02:45 -04:00
Axionize 8a2f85fadc Finally fix wrong KB being applied bug 2024-10-24 16:31:27 -04:00
Axionize 24b32f2eca Fix Conversion from Fabric to Packetevents BlockStates 2024-10-24 15:51:41 -04:00
Axionize 7bfe5c584f Fix Conversion from Bukkit to Packetevents BlockState conversion for Spigot 2024-10-24 15:44:29 -04:00
Axionize e904f44a5f Fix new custom updateInterval for players 2024-10-24 15:10:16 -04:00
Axionize 9797780d81 Use the more efficient Vector3D over Location for bounding box calculations 2024-10-24 14:29:16 -04:00
Axionize 9da919e910 Make BukkitPlayerDamageListener use early return paradigm like the cross-platform code 2024-10-24 14:15:15 -04:00
Axionize fe395f2cbd Revert to using getHitBlockPosition() instead of getHitPosition() 2024-10-22 06:56:01 -04:00
Axionize 581bef78fe Change packet ids 2024-10-22 06:11:54 -04:00
Axionize 35e19c56b1 Fix Bounding box calculation logic to use old algorithm 2024-10-22 06:09:44 -04:00
CASELOAD7000 e2514fd229 Rename classes 2024-10-21 21:41:37 -05:00
CASELOAD7000 1fceee5319 Remove var usage 2024-10-21 21:32:29 -05:00
CASELOAD7000 07b34d4744 Refactor getPlatform 2024-10-21 21:27:22 -05:00
CASELOAD7000 b8ac667e5f Another test 2024-10-21 21:07:52 -05:00
BuildTools a467a06df4 test 2024-10-21 20:49:13 -05:00
Axionize 50bde1375a First attempt at Custom entity tick intervals 2024-10-21 19:53:11 -04:00
Axionize 6ce4469db9 Add event system, refactor some configs to use, tood rest later 2024-10-21 18:09:20 -04:00
Axionize d63e4a0c29 Mark fabric dev builds as alpha instead of beta automatically 2024-10-21 16:46:39 -04:00
Axionize e5b70523bc More robust logging when reflection fails 2024-10-21 16:44:08 -04:00
Axionize cf0e5decd7 Attempt to fix flying KB introduced in b45 2024-10-21 16:32:39 -04:00
Axionize cbe1c3c46e Fix gradle wrapper support 2024-10-20 23:21:08 -04:00
Axionize 1db1e02c8b Fix support for Paper 1.20.6+ 2024-10-20 03:59:51 -04:00
Axionize 7a9fee6e2d 1.12.2 backport 2024-10-20 01:50:48 -04:00
Axionize addb077133 1.14.4 support 2024-10-19 23:24:33 -04:00
Axionize 1fcbd33a62 Merge branch 'dev' into dev-upstream 2024-10-19 22:09:55 -04:00
Axionize be88498fe5 Merge branch 'dev' into dev-upstream 2024-10-18 16:46:36 -04:00
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
78 changed files with 1735 additions and 619 deletions

No files matched your search

+12 -9
View File
@@ -24,6 +24,7 @@ jobs:
with:
java-version: '21'
distribution: 'adopt'
cache: gradle
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
@@ -77,6 +78,8 @@ jobs:
- name: Build with Gradle
run: gradle clean build -x test
env:
GITHUB_REPOSITORY: ${{ github.repository }}
- name: Rename shaded jar to overwrite non-shaded jar
run: mv bukkit/build/libs/knockbacksync-bukkit-${{ env.NEW_VERSION }}-all.jar bukkit/build/libs/knockbacksync-bukkit-${{ env.NEW_VERSION }}.jar
@@ -115,7 +118,7 @@ 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/${{ github.repository }}/releases/latest/download/dev-builds.txt
# Create a copy of the original file
cp dev-builds.txt dev-builds.txt.original
@@ -142,22 +145,22 @@ jobs:
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)
RELEASE_ID=$(curl -s "https://api.github.com/repos/${{ github.repository }}/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" | \
"https://api.github.com/repos/${{ github.repository }}/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"
"https://api.github.com/repos/${{ github.repository }}/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"
"https://uploads.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID/assets?name=dev-builds.txt"
fi
# Clean up
@@ -184,7 +187,7 @@ jobs:
- uses: Kir-Antipov/mc-publish@v3.3
with:
modrinth-id: wGTbjSTq
modrinth-id: ${{ vars.MODRINTH_ID }}
modrinth-featured: true
modrinth-unfeature-mode: subset
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
@@ -204,7 +207,7 @@ jobs:
purpur
game-versions: |
>=1.16.5
>=1.12.2
retry-attempts: 2
retry-delay: 10000
@@ -212,7 +215,7 @@ jobs:
- uses: Kir-Antipov/mc-publish@v3.3
with:
modrinth-id: wGTbjSTq
modrinth-id: ${{ vars.MODRINTH_ID }}
modrinth-featured: true
modrinth-unfeature-mode: subset
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
@@ -222,7 +225,7 @@ jobs:
name: KnockbackSync-${{ env.NEW_VERSION }}
version: ${{ env.NEW_VERSION }}
version-type: beta
version-type: alpha
loaders: |
fabric
-1
View File
@@ -9,7 +9,6 @@ target/
.gradle/
gradle/
build/
run/
+5 -9
View File
@@ -1,4 +1,4 @@
Project Structure
## Project Structure
- `common` submodule contains shared code for all platforms.
- It contains the interfaces and abstractions for
@@ -6,11 +6,11 @@ Project Structure
- Scheduling
- Players
- Blocks
- World
- Worlds/Levels
- The Server
- In addition to the interfaces, the code also contains the implementations for
- Commands
- Commands use Brigadier with Reflection to handle version differences. Simply call our command code in common by registering KnockBackSyncCommand.build() and implementing CommandOperations
- Commands use incendio's `cloud` command system. All you have to do to add platform support is to create a CommandManager and call register().
- Handling Events
- Gathering Statistics
- `fabric` contains code for latest fabric version (currently 1.21)
@@ -21,6 +21,7 @@ If you want to add support for a new platform, simply make a new submodule and i
- Checking for Permissions
- Listening for events and calling the right handler
- Players + Blocks + World + Server
- Metrics
You will need to handle events when Players:
- join/leave
@@ -31,9 +32,4 @@ If you find your platform is lacking events, take a look at how the PlayerVeloci
## List of version and platform-specific caveats
- Runnable and manual ping does not work on fabric right now due b/c packetevents. Ping measurement will be less accurate
- Command System
- context.getSource().sendSuccess() takes a Supplier<Component> in > 1.19.4
- context.getSource().sendSuccess() takes a Component in <= 1.19.4
- Component.literal() does not exist in <= 1.18.2
- Runnable and manual ping does not work on fabric right now due b/c packetevents. Ping measurement will be less accurate
-12
View File
@@ -18,24 +18,12 @@ 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.
;
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 |
|------------------|-----------------------------------|--------|-------------|-----------------|
+2
View File
@@ -8,6 +8,7 @@ plugins {
val fullVersion = "1.3.5"
val snapshot = true
val githubRepo = System.getenv("GITHUB_REPOSITORY") ?: project.findProperty("githubRepo").toString()
allprojects {
fun getVersionMeta(includeHash: Boolean): String {
@@ -29,6 +30,7 @@ allprojects {
group = "me.caseload.knockbacksync"
version = "$fullVersion${getVersionMeta(true)}"
ext["versionNoHash"] = "$fullVersion${getVersionMeta(false)}"
ext["githubRepo"] = githubRepo
repositories {
mavenLocal()
+5 -4
View File
@@ -29,24 +29,25 @@ tasks.named<JavaCompile>("compileTestJava") {
dependencies {
implementation(project(":common"))
compileOnly("org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT")
compileOnly("org.spigotmc:spigot-api:1.18.2-R0.1-SNAPSHOT")
compileOnly("org.geysermc.floodgate:api:2.0-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("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")!!)
// Required for 1.14.4 support because gson is too old to have JosnParser.parseString()
shadeThisThing(implementation("com.google.code.gson:gson:2.11.0")!!)
}
tasks.withType<ShadowJar> {
configurations = listOf(shadeThisThing)
isEnableRelocation = true
isEnableRelocation = false
relocationPrefix = "${project.property("maven_group")}.${project.property("archives_base_name")}.shaded"
}
@@ -1,10 +1,11 @@
package me.caseload.knockbacksync;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
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.event.events.ConfigReloadEvent;
import me.caseload.knockbacksync.event.KBSyncEventHandler;
import me.caseload.knockbacksync.listener.bukkit.*;
import me.caseload.knockbacksync.manager.ConfigManager;
import me.caseload.knockbacksync.permission.PermissionChecker;
import me.caseload.knockbacksync.permission.PluginPermissionChecker;
@@ -16,6 +17,9 @@ 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.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
@@ -25,21 +29,28 @@ import org.incendo.cloud.paper.LegacyPaperCommandManager;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.logging.Logger;
public class KBSyncBukkitBase extends KnockbackSyncBase {
public class BukkitBase extends Base {
private final JavaPlugin plugin;
private final BukkitSenderFactory bukkitSenderFactory = new BukkitSenderFactory(this);
private final PluginPermissionChecker permissionChecker = new PluginPermissionChecker();
public KBSyncBukkitBase(JavaPlugin plugin) {
private int playerUpdateInterval;
public BukkitBase(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());
this.playerUpdateInterval = this.getConfigManager().getConfigWrapper().getInt("entity_tick_intervals.player", 2);
}
@Override
@@ -67,9 +78,15 @@ public class KBSyncBukkitBase extends KnockbackSyncBase {
public void enable() {
super.enable();
initializeScheduler();
saveDefaultConfig();
configManager.updateConfig();
configManager.loadConfig(false);
statsManager.init();
checkForUpdates();
super.simpleEventBus.registerListeners(this);
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_20_5)) {
scheduler.runTaskTimerAsynchronously(this::setUpdateIntervals, 1, 1);
}
}
@Override
@@ -135,4 +152,45 @@ public class KBSyncBukkitBase extends KnockbackSyncBase {
public BukkitSenderFactory getSenderFactory() {
return this.bukkitSenderFactory;
}
public void setUpdateIntervals() {
try {
for (World world : Bukkit.getWorlds()) {
Method getWorldHandleMethod = world.getClass().getMethod("getHandle");
Object serverLevel = getWorldHandleMethod.invoke(world);
// Get ChunkMap
Method getChunkSource = serverLevel.getClass().getMethod("getChunkSource");
Object chunkSource = getChunkSource.invoke(serverLevel);
Field chunkMapField = chunkSource.getClass().getDeclaredField("chunkMap");
chunkMapField.setAccessible(true);
Object chunkMap = chunkMapField.get(chunkSource);
// Get entityMap from ChunkMap
Field entityMapField = chunkMap.getClass().getDeclaredField("entityMap");
entityMapField.setAccessible(true);
Map<Integer, ?> entityMap = (Map<Integer, ?>) entityMapField.get(chunkMap);
for (Player player : Bukkit.getOnlinePlayers()) {
Object trackedEntity = entityMap.get(player.getEntityId());
if (trackedEntity == null)
continue;
Field serverEntityField = trackedEntity.getClass().getDeclaredField("serverEntity");
serverEntityField.setAccessible(true);
Object serverEntity = serverEntityField.get(trackedEntity);
Field updateIntervalField = serverEntity.getClass().getDeclaredField("updateInterval");
updateIntervalField.setAccessible(true);
updateIntervalField.set(serverEntity, playerUpdateInterval);
}
}
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | NoSuchFieldException e) {
throw new IllegalStateException("Unable to use reflection to modify updateIntervals" + e);
}
}
@KBSyncEventHandler
public void onConfigReload(ConfigReloadEvent event) {
playerUpdateInterval = event.getConfigManager().getConfigWrapper().getInt("entity_tick_intervals.player", 2);
}
}
@@ -3,9 +3,9 @@ package me.caseload.knockbacksync;
import com.github.retrooper.packetevents.PacketEvents;
import org.bukkit.plugin.java.JavaPlugin;
public final class KBSyncBukkitLoaderPlugin extends JavaPlugin {
public final class BukkitLoaderPlugin extends JavaPlugin {
private final KnockbackSyncBase core = new KBSyncBukkitBase(this);
private final Base core = new BukkitBase(this);
@Override
public void onLoad() {
@@ -15,9 +15,12 @@ public class BukkitPlayerDamageListener extends PlayerDamageListener implements
public void onPlayerDamage(EntityDamageByEntityEvent event) {
Entity victim = event.getEntity();
Entity attacker = event.getDamager();
if ((victim instanceof Player) && (attacker instanceof Player))
if (!MultiLibUtil.isExternalPlayer((Player) victim)) {
onPlayerDamage(new BukkitPlayer((Player) victim), new BukkitPlayer((Player) attacker));
}
if (!(victim instanceof Player) || !(attacker instanceof Player))
return;
if (MultiLibUtil.isExternalPlayer((Player) victim))
return;
onPlayerDamage(new BukkitPlayer((Player) victim), new BukkitPlayer((Player) attacker));
}
}
@@ -1,15 +1,29 @@
package me.caseload.knockbacksync.listener.bukkit;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import me.caseload.knockbacksync.listener.PlayerJoinQuitListener;
import me.caseload.knockbacksync.player.BukkitPlayer;
import me.caseload.knockbacksync.player.PlayerData;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class BukkitPlayerJoinQuitListener extends PlayerJoinQuitListener implements Listener {
public static List<Object> chunkMapList = new ArrayList<>();
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
onPlayerJoin(new PlayerData(new BukkitPlayer(event.getPlayer())));
@@ -34,8 +34,7 @@ public class BukkitPlayerKnockbackListener extends PlayerKnockbackListener imple
if (MultiLibUtil.isExternalPlayer(victim))
return;
Vector vector = event.getVelocity();
Vector vector = victim.getVelocity();
onPlayerVelocity(new BukkitPlayer(victim), new Vector3d(vector.getX(), vector.getY(), vector.getZ()));
// callback.setVelocity(victim.getVelocity()); // Update the callback's velocity in Spigot
}
}
@@ -1,20 +1,72 @@
package me.caseload.knockbacksync.player;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.world.BoundingBox;
import com.github.retrooper.packetevents.util.Vector3d;
import me.caseload.knockbacksync.world.PlatformWorld;
import me.caseload.knockbacksync.world.SpigotWorld;
import org.bukkit.Location;
import org.bukkit.Bukkit;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.UUID;
public class BukkitPlayer implements PlatformPlayer {
public final Player bukkitPlayer;
// Reflection variables
private static Class<?> craftPlayerClass;
private static Method getHandleMethod;
private static Method getAttackStrengthScaleMethod;
private static ServerVersion currentVersion = PacketEvents.getAPI().getServerManager().getVersion();
// 1.12.2 support
static {
try {
// Check the current server versio
// If the version is greater than 1.14.4, use the Player method directly
if (currentVersion.isOlderThan(ServerVersion.V_1_15)) {
Object server = Bukkit.getServer().getClass().getDeclaredMethod("getServer").invoke(Bukkit.getServer());
String nmsPackage = server.getClass().getPackage().getName();
String bukkitPackage = Bukkit.getServer().getClass().getPackage().getName();
// Step 1: Load the CraftPlayer class
craftPlayerClass = Class.forName(bukkitPackage + ".entity.CraftPlayer");
// Step 2: Get the getHandle method
getHandleMethod = craftPlayerClass.getMethod("getHandle");
// Step 3: Get the getAttackStrengthScale method from the EntityPlayer class
Class<?> entityPlayerClass = Class.forName(nmsPackage + ".EntityPlayer");
String getAttackStrengthScaleMethodName = "";
// Determine the method name based on the version
if (currentVersion.isOlderThan(ServerVersion.V_1_13)) {
getAttackStrengthScaleMethodName = "n";
} else if (currentVersion.isOlderThan(ServerVersion.V_1_14)) {
getAttackStrengthScaleMethodName = "r";
} else if (currentVersion.isOlderThan(ServerVersion.V_1_15)) {
getAttackStrengthScaleMethodName = "s";
}
// Get the attack strength scale method for EntityPlayer
getAttackStrengthScaleMethod = entityPlayerClass.getMethod(getAttackStrengthScaleMethodName, float.class);
getAttackStrengthScaleMethod.setAccessible(true);
}
} catch (ClassNotFoundException | NoSuchMethodException e) {
throw new IllegalStateException("Method of Class required to support this version not found via reflection" + e);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException("Cannot access required methods via reflection to support this version" + e);
}
}
public BukkitPlayer(Player player) {
this.bukkitPlayer = player;
}
@@ -67,7 +119,11 @@ public class BukkitPlayer implements PlatformPlayer {
@Override
public int getPing() {
return bukkitPlayer.getPing();
if (currentVersion.isNewerThanOrEquals(ServerVersion.V_1_16_5)) {
return bukkitPlayer.getPing();
} else {
return PacketEvents.getAPI().getPlayerManager().getPing(bukkitPlayer);
}
}
@Override
@@ -82,7 +138,7 @@ public class BukkitPlayer implements PlatformPlayer {
@Override
public Vector3d getLocation() {
Location location = bukkitPlayer.getLocation();
org.bukkit.Location location = bukkitPlayer.getLocation();
return new Vector3d(location.getX(), location.getY(), location.getZ());
}
@@ -93,7 +149,19 @@ public class BukkitPlayer implements PlatformPlayer {
@Override
public double getAttackCooldown() {
return bukkitPlayer.getAttackCooldown();
if (currentVersion.isNewerThan(ServerVersion.V_1_14_4)) {
return bukkitPlayer.getAttackCooldown();
} else {
try {
// Step 1: Get the CraftPlayer instance
// Step 2: Get the handle (NMS EntityPlayer)
Object entityPlayer = getHandleMethod.invoke(bukkitPlayer);
// Step 3: Invoke the getAttackStrengthScale method
return (float) getAttackStrengthScaleMethod.invoke(entityPlayer, 0.5f);
} catch (Exception e) {
throw new IllegalStateException("This plugin will not work. NMS mapping for getAttackCooldown() failed!");
}
}
}
@Override
@@ -115,4 +183,29 @@ public class BukkitPlayer implements PlatformPlayer {
public void setVelocity(Vector3d adjustedVelocity) {
bukkitPlayer.setVelocity(new Vector(adjustedVelocity.x, adjustedVelocity.y, adjustedVelocity.z));
}
@Override
public Vector3d getVelocity() {
final Vector bukkitVelocity = bukkitPlayer.getVelocity();
return new Vector3d(bukkitVelocity.getX(), bukkitVelocity.getY(), bukkitVelocity.getZ());
}
@Override
public double getJumpPower() {
double jumpVelocity = 0.42;
PotionEffect jumpEffect = bukkitPlayer.getPotionEffect(PotionEffectType.JUMP);
if (jumpEffect != null) {
int amplifier = jumpEffect.getAmplifier();
jumpVelocity += (amplifier + 1) * 0.1F;
}
return jumpVelocity;
}
@Override
public BoundingBox getBoundingBox() {
org.bukkit.util.BoundingBox boundingBox = bukkitPlayer.getBoundingBox();
return new BoundingBox(boundingBox.getMinX(), boundingBox.getMinY(), boundingBox.getMinZ(), boundingBox.getMaxX(), boundingBox.getMaxY(), boundingBox.getMaxZ());
}
}
@@ -1,6 +1,6 @@
package me.caseload.knockbacksync.sender;
import me.caseload.knockbacksync.KBSyncBukkitBase;
import me.caseload.knockbacksync.BukkitBase;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.RemoteConsoleCommandSender;
@@ -12,10 +12,10 @@ import org.incendo.cloud.SenderMapper;
import java.util.UUID;
public class BukkitSenderFactory extends SenderFactory<KBSyncBukkitBase, CommandSender> implements SenderMapper<CommandSender, Sender> {
public class BukkitSenderFactory extends SenderFactory<BukkitBase, CommandSender> implements SenderMapper<CommandSender, Sender> {
// private final BukkitAudiences audiences;
public BukkitSenderFactory(KBSyncBukkitBase kbSyncBukkitBase) {
public BukkitSenderFactory(BukkitBase kbSyncBukkitBase) {
super(kbSyncBukkitBase);
// this.audiences = BukkitAudiences.create(kbSyncBukkitBase.getLoader());
}
@@ -1,12 +1,12 @@
package me.caseload.knockbacksync.stats.custom;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
public class BukkitStatsManager extends StatsManager {
@Override
public void init() {
KnockbackSyncBase.INSTANCE.getScheduler().runTaskAsynchronously(() -> {
Base.INSTANCE.getScheduler().runTaskAsynchronously(() -> {
BuildTypePie.determineBuildType(); // Function to calculate hash
MetricsBukkit metrics = new MetricsBukkit(23568);
metrics.addCustomChart(new PlayerVersionsPie());
@@ -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.BukkitLoaderPlugin;
import me.caseload.knockbacksync.Base;
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 = BukkitLoaderPlugin.getPlugin(BukkitLoaderPlugin.class);
// Get the config file
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
File configFile = new File(bStatsFolder, "config.yml");
@@ -91,7 +91,7 @@ public class MetricsBukkit implements Metrics {
enabled,
this::appendPlatformData,
this::appendServiceData,
submitDataTask -> KnockbackSyncBase.INSTANCE.getScheduler().runTask(submitDataTask),
submitDataTask -> Base.INSTANCE.getScheduler().runTask(submitDataTask),
// isFolia
// ? null
// : submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask),
@@ -1,19 +1,59 @@
package me.caseload.knockbacksync.world;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3i;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import me.caseload.knockbacksync.world.raytrace.FluidHandling;
import me.caseload.knockbacksync.world.raytrace.RayTraceResult;
import org.bukkit.Bukkit;
import org.bukkit.FluidCollisionMode;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.util.Vector;
import java.lang.reflect.Method;
public class SpigotWorld implements PlatformWorld {
private final World world;
// Reflection variables
private static Class<?> craftWorldClass;
private static Class<?> vec3DClass;
private static Class<?> movingObjectPositionClass;
private static Class<?> enumDirectionClass;
private static Method craftWorldGetHandleMethod;
private static Method rayTraceMethod;
private static Method vec3DAddMethod;
static {
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_13)) {
try {
Object server = Bukkit.getServer().getClass().getDeclaredMethod("getServer").invoke(Bukkit.getServer());
String nmsPackage = server.getClass().getPackage().getName();
String bukkitPackage = Bukkit.getServer().getClass().getPackage().getName();
// Load necessary classes dynamically for Minecraft 1.12.2
craftWorldClass = Class.forName(bukkitPackage + ".CraftWorld");
vec3DClass = Class.forName(nmsPackage + ".Vec3D");
movingObjectPositionClass = Class.forName(nmsPackage + ".MovingObjectPosition");
enumDirectionClass = Class.forName(nmsPackage + ".EnumDirection");
// CraftWorld
// Get method
craftWorldGetHandleMethod = craftWorldClass.getMethod("getHandle");
rayTraceMethod = Class.forName(nmsPackage + ".World").getMethod("rayTrace", vec3DClass, vec3DClass, boolean.class, boolean.class, boolean.class);
vec3DAddMethod = vec3DClass.getMethod("add", double.class, double.class, double.class);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public SpigotWorld(World world) {
this.world = world;
}
@@ -40,7 +80,7 @@ public class SpigotWorld implements PlatformWorld {
@Override
public WrappedBlockState getBlockStateAt(int x, int y, int z) {
Block block = world.getBlockAt(x, y, z);
return WrappedBlockState.getByString(block.getType().name());
return SpigotConversionUtil.fromBukkitBlockData(block.getBlockData());
}
@Override
@@ -50,22 +90,80 @@ public class SpigotWorld implements PlatformWorld {
@Override
public RayTraceResult rayTraceBlocks(Vector3d start, Vector3d direction, double maxDistance, FluidHandling fluidHandling, boolean ignorePassableBlocks) {
Vector startVec = new Vector(start.getX(), start.getY(), start.getZ());
Vector directionVec = new Vector(direction.getX(), direction.getY(), direction.getZ());
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThan(ServerVersion.V_1_12_2)) {
Vector startVec = new Vector(start.getX(), start.getY(), start.getZ());
Vector directionVec = new Vector(direction.getX(), direction.getY(), direction.getZ());
FluidCollisionMode fluidMode = (fluidHandling == FluidHandling.NONE) ? FluidCollisionMode.NEVER :
(fluidHandling == FluidHandling.SOURCE_ONLY) ? FluidCollisionMode.SOURCE_ONLY :
FluidCollisionMode.ALWAYS;
FluidCollisionMode fluidMode = (fluidHandling == FluidHandling.NONE) ? FluidCollisionMode.NEVER :
(fluidHandling == FluidHandling.SOURCE_ONLY) ? FluidCollisionMode.SOURCE_ONLY :
FluidCollisionMode.ALWAYS;
org.bukkit.util.RayTraceResult result = world.rayTraceBlocks(startVec.toLocation(world), directionVec, maxDistance, fluidMode, ignorePassableBlocks);
org.bukkit.util.RayTraceResult result = world.rayTraceBlocks(startVec.toLocation(world), directionVec, maxDistance, fluidMode, ignorePassableBlocks);
if (result == null) return null;
if (result == null) return null;
return new RayTraceResult(
new Vector3d(result.getHitPosition().getX(), result.getHitPosition().getY(), result.getHitPosition().getZ()),
getBlockFaceFrom(result.getHitBlockFace()),
new Vector3i(result.getHitBlock().getX(), result.getHitBlock().getY(), result.getHitBlock().getZ()),
result.getHitBlock() != null ? WrappedBlockState.getByString(result.getHitBlock().getType().name()) : null
);
return new RayTraceResult(
new Vector3d(result.getHitPosition().getX(), result.getHitPosition().getY(), result.getHitPosition().getZ()),
getBlockFaceFrom(result.getHitBlockFace()),
new Vector3i(result.getHitBlock().getX(), result.getHitBlock().getY(), result.getHitBlock().getZ()),
result.getHitBlock() != null ? SpigotConversionUtil.fromBukkitBlockData(result.getHitBlock().getBlockData()) : null
);
// Only tested on 1.12.2
} else {
try {
// Create start and end positions
Object startPos = vec3DClass.getConstructor(double.class, double.class, double.class)
.newInstance(start.getX(), start.getY(), start.getZ());
Object endPos = vec3DAddMethod.invoke(startPos, direction.getX(), direction.getY(), direction.getZ());
// Call rayTrace method
Object hitResult = rayTraceMethod.invoke(craftWorldGetHandleMethod.invoke(world), startPos, endPos, false, true, false);
if (hitResult == null) return null;
// Extract hit position and direction
Vector3d hitPosition = new Vector3d(
(double) hitResult.getClass().getField("pos").get(hitResult),
(double) hitResult.getClass().getField("pos").get(hitResult).getClass().getField("y").get(hitResult.getClass().getField("pos").get(hitResult)),
(double) hitResult.getClass().getField("pos").get(hitResult).getClass().getField("z").get(hitResult.getClass().getField("pos").get(hitResult))
);
Object hitDirection = hitResult.getClass().getField("direction").get(hitResult);
Object hitBlock = hitResult.getClass().getField("e").get(hitResult); // e = BlockHitResult
return new RayTraceResult(
hitPosition,
getHitBlockFace(hitDirection),
new Vector3i((int) hitBlock.getClass().getField("x").get(hitBlock),
(int) hitBlock.getClass().getField("y").get(hitBlock),
(int) hitBlock.getClass().getField("z").get(hitBlock)),
hitBlock != null ? WrappedBlockState.getByString(hitBlock.getClass().getField("type").get(hitBlock).toString()) : null
);
} catch (Exception e) {
e.printStackTrace();
return null; // Return null if there was an error during ray tracing
}
}
}
private static BlockFace getHitBlockFace(Object enumDirection) {
try {
// Match the EnumDirection to the corresponding BlockFace
if (enumDirection.equals(SpigotWorld.enumDirectionClass.getField("NORTH").get(null))) {
return BlockFace.NORTH;
} else if (enumDirection.equals(SpigotWorld.enumDirectionClass.getField("SOUTH").get(null))) {
return BlockFace.SOUTH;
} else if (enumDirection.equals(SpigotWorld.enumDirectionClass.getField("EAST").get(null))) {
return BlockFace.EAST;
} else if (enumDirection.equals(SpigotWorld.enumDirectionClass.getField("WEST").get(null))) {
return BlockFace.WEST;
} else if (enumDirection.equals(SpigotWorld.enumDirectionClass.getField("UP").get(null))) {
return BlockFace.UP;
} else if (enumDirection.equals(SpigotWorld.enumDirectionClass.getField("DOWN").get(null))) {
return BlockFace.DOWN;
}
} catch (Exception e) {
e.printStackTrace();
}
throw new IllegalStateException("Unexpected value: " + enumDirection);
}
}
+7 -3
View File
@@ -1,8 +1,8 @@
name: KnockbackSync
version: ${version}
main: me.caseload.knockbacksync.KBSyncBukkitLoaderPlugin
api-version: '1.16'
authors: [ Caseload ]
main: me.caseload.knockbacksync.BukkitLoaderPlugin
api-version: '1.13' # We actually support 1.12.2, api-version was added later though
authors: [ Caseload, Axionize ]
description: Synchronizes player knockback for smoother gameplay.
folia-supported: true
permissions:
@@ -15,4 +15,8 @@ permissions:
knockbacksync.toggle.self:
default: op
knockbacksync.ping:
default: op
knockbacksync.reload:
default: op
knockbacksync.toggleoffground:
default: op
+8 -35
View File
@@ -1,6 +1,6 @@
plugins {
id("fabric-loom")
// id("net.neoforged.moddev") version "1.0.11"
id("com.github.gmazzo.buildconfig") version "3.1.0"
}
dependencies {
@@ -17,45 +17,14 @@ dependencies {
// Shaded in or bundled by platform-specific code
implementation("com.github.retrooper:packetevents-api:2.5.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.17.2")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.2")
implementation("org.yaml:snakeyaml:2.0")
// implementation("com.fasterxml.jackson.core:jackson-databind:2.17.2")
// implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.2")
implementation("org.kohsuke:github-api:1.326") {
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")
}
@@ -66,4 +35,8 @@ repositories {
tasks.named("remapJar").configure {
enabled = false
}
buildConfig {
buildConfigField("String", "GITHUB_REPO", "\"${project.rootProject.ext["githubRepo"]}\"")
}
@@ -9,6 +9,10 @@ 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 me.caseload.knockbacksync.command.subcommand.ToggleOffGroundSubcommand;
import me.caseload.knockbacksync.event.Event;
import me.caseload.knockbacksync.event.EventBus;
import me.caseload.knockbacksync.event.OptimizedEventBus;
import me.caseload.knockbacksync.listener.packetevents.AttributeChangeListener;
import me.caseload.knockbacksync.listener.packetevents.PingReceiveListener;
import me.caseload.knockbacksync.manager.ConfigManager;
@@ -23,14 +27,13 @@ import org.kohsuke.github.GitHub;
import java.io.File;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.*;
import java.util.logging.Logger;
// Base class
public abstract class KnockbackSyncBase {
public abstract class Base {
public static Logger LOGGER;
public static KnockbackSyncBase INSTANCE;
public static Base INSTANCE;
public final Platform platform;
public StatsManager statsManager;
@@ -39,35 +42,37 @@ public abstract class KnockbackSyncBase {
@Getter protected SchedulerAdapter scheduler;
@Getter protected ConfigManager configManager;
protected CommandManager<Sender> commandManager;
@Getter protected final EventBus simpleEventBus = new OptimizedEventBus();
@Getter
protected AbstractPlayerSelectorParser<Sender> playerSelectorParser;
protected KnockbackSyncBase() {
protected Base() {
this.platform = getPlatform();
INSTANCE = this;
}
private Platform getPlatform() {
try {
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
return Platform.FOLIA; // Paper (Folia) detected
} catch (ClassNotFoundException ignored1) {
}
final Map<String, Platform> platforms = Collections.unmodifiableMap(new HashMap<String, Platform>() {{
put("io.papermc.paper.threadedregions.RegionizedServer", Platform.FOLIA);
put("org.bukkit.Bukkit", Platform.BUKKIT);
put("net.fabricmc.loader.api.FabricLoader", Platform.FABRIC);
}});
try {
Class.forName("org.bukkit.Bukkit");
return Platform.BUKKIT; // Bukkit (Spigot/Paper without Folia) detected
} catch (ClassNotFoundException ignored2) {
}
return platforms.entrySet().stream()
.filter(entry -> isClassPresent(entry.getKey()))
.map(Map.Entry::getValue)
.findFirst()
.orElseThrow(() -> new IllegalStateException("Unknown platform!"));
}
private boolean isClassPresent(String className) {
try {
Class.forName("net.fabricmc.loader.api.FabricLoader");
return Platform.FABRIC; // Fabric detected
} catch (ClassNotFoundException ignored3) {
Class.forName(className);
return true;
} catch (ClassNotFoundException ignored) {
return false;
}
throw new IllegalStateException("Unknown platform!");
}
public abstract Logger getLogger();
@@ -102,6 +107,7 @@ public abstract class KnockbackSyncBase {
new AttributeChangeListener(),
new PingReceiveListener()
);
Event.setEventBus(simpleEventBus);
}
protected abstract void registerPlatformListeners();
@@ -112,6 +118,7 @@ public abstract class KnockbackSyncBase {
new ReloadCommand(),
new PingCommand(),
new StatusCommand(),
new ToggleOffGroundSubcommand(),
new ToggleCommand()
);
list.forEach(command -> command.register(commandManager));
@@ -1,6 +1,6 @@
package me.caseload.knockbacksync.command.subcommand;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.command.generic.BuilderCommand;
import me.caseload.knockbacksync.command.generic.PlayerSelector;
import me.caseload.knockbacksync.sender.Sender;
@@ -23,7 +23,7 @@ public class PingCommand implements BuilderCommand {
return PredicatePermission.of(senderPredicate).testPermission(sender);
}))
.literal("ping")
.optional("target", KnockbackSyncBase.INSTANCE.getPlayerSelectorParser().descriptor())
.optional("target", Base.INSTANCE.getPlayerSelectorParser().descriptor())
.handler(context -> {
PlayerSelector targetSelector = context.getOrDefault("target", null);
@@ -39,4 +39,6 @@ public class PingCommand implements BuilderCommand {
})
);
}
}
@@ -1,7 +1,9 @@
package me.caseload.knockbacksync.command.subcommand;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.command.generic.BuilderCommand;
import me.caseload.knockbacksync.event.events.ConfigReloadEvent;
import me.caseload.knockbacksync.event.KBSyncEventHandler;
import me.caseload.knockbacksync.manager.ConfigManager;
import me.caseload.knockbacksync.sender.Sender;
import me.caseload.knockbacksync.util.ChatUtil;
@@ -11,27 +13,38 @@ import org.incendo.cloud.permission.PredicatePermission;
import java.util.function.Predicate;
public class ReloadCommand implements BuilderCommand {
private final ConfigManager configManager = Base.INSTANCE.getConfigManager();
private String rawReloadMessage = configManager.getConfigWrapper().getString("reload_message", "&aSuccessfully reloaded KnockbackSync.");
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);
};
.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);
return PredicatePermission.of(senderPredicate).testPermission(sender);
}))
.handler(context -> {
configManager.loadConfig(true);
String rawReloadMessage = configManager.getReloadMessage();
String reloadMessage = ChatUtil.translateAlternateColorCodes('&', rawReloadMessage);
// Fire the ConfigReloadEvent
new ConfigReloadEvent(configManager).post();
context.sender().sendMessage(reloadMessage);
})
String reloadMessage = ChatUtil.translateAlternateColorCodes('&', rawReloadMessage);
context.sender().sendMessage(reloadMessage);
})
);
Base.INSTANCE.getSimpleEventBus().registerListeners(this);
}
}
@KBSyncEventHandler
public void onConfigReload(ConfigReloadEvent event) {
rawReloadMessage = event.getConfigManager().getConfigWrapper().getString("reload_message", "&aSuccessfully reloaded KnockbackSync.");
}
}
@@ -1,8 +1,11 @@
package me.caseload.knockbacksync.command.subcommand;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.ConfigWrapper;
import me.caseload.knockbacksync.command.generic.BuilderCommand;
import me.caseload.knockbacksync.command.generic.PlayerSelector;
import me.caseload.knockbacksync.event.events.ConfigReloadEvent;
import me.caseload.knockbacksync.event.KBSyncEventHandler;
import me.caseload.knockbacksync.manager.ConfigManager;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.player.PlatformPlayer;
@@ -16,15 +19,23 @@ import java.util.function.Predicate;
public class StatusCommand implements BuilderCommand {
private static final ConfigManager configManager = KnockbackSyncBase.INSTANCE.getConfigManager();
private static final ConfigManager configManager = Base.INSTANCE.getConfigManager();
private static final String STATUS_SELF_PERMISSION = "knockbacksync.status.self";
private static final String STATUS_OTHER_PERMISSION = "knockbacksync.status.other";
private String globalStatusMessage;
private String playerStatusMessage;
private String playerDisabledGlobalMessage;
public StatusCommand() {
updateConfigValues();
}
public void register(CommandManager<Sender> manager) {
manager.command(
manager.commandBuilder("knockbacksync", "kbsync", "kbs")
.literal("status")
.optional("target", KnockbackSyncBase.INSTANCE.getPlayerSelectorParser().descriptor())
.optional("target", Base.INSTANCE.getPlayerSelectorParser().descriptor())
.permission((sender -> {
Predicate<Sender> senderPredicate = (s) -> {
return s.hasPermission(STATUS_SELF_PERMISSION, true) || sender.hasPermission(STATUS_OTHER_PERMISSION, false);
@@ -35,53 +46,61 @@ public class StatusCommand implements BuilderCommand {
.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 (targetSelector == null) {
// Show global status
boolean globalStatus = configManager.isToggled();
sender.sendMessage(ChatUtil.translateAlternateColorCodes('&', globalStatusMessage) +
(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);
showPlayerStatus(sender, Base.INSTANCE.platformServer.getPlayer(sender.getUniqueId()));
}
} else {
sender.sendMessage(ChatUtil.translateAlternateColorCodes('&', "&cYou do not have permisssion to check your knockbacksync status."));
}
} else {
if (sender.hasPermission(STATUS_OTHER_PERMISSION)) {
if (sender.hasPermission(STATUS_OTHER_PERMISSION, true)) {
PlatformPlayer target = targetSelector.getSinglePlayer();
showPlayerStatus(sender, target, configManager);
showPlayerStatus(sender, target);
} else {
sender.sendMessage(ChatUtil.translateAlternateColorCodes('&', "&cYou do not have permisssion to check the status of other players!"));
sender.sendMessage(ChatUtil.translateAlternateColorCodes('&', "&cYou do not have permission to check the status of other players!"));
}
}
})
);
Base.INSTANCE.getSimpleEventBus().registerListeners(this);
}
private static void showPlayerStatus(Sender sender, PlatformPlayer target, ConfigManager configManager) {
private void showPlayerStatus(Sender sender, PlatformPlayer target) {
boolean globalStatus = configManager.isToggled();
UUID uuid = target.getUUID();
boolean playerStatus = PlayerDataManager.containsPlayerData(uuid);
String statusMessage = ChatUtil.translateAlternateColorCodes('&', playerStatusMessage.replace("%player%", target.getName()));
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)"))));
sender.sendMessage(statusMessage + ChatUtil.translateAlternateColorCodes('&', playerDisabledGlobalMessage));
} else {
sender.sendMessage(ChatUtil.translateAlternateColorCodes('&',
configManager.getConfigWrapper().getString("player_status_message", "&e%player%'s KnockbackSync status: ")
.replace("%player%", target.getName()) +
sender.sendMessage(statusMessage +
(playerStatus
? ChatUtil.translateAlternateColorCodes('&', "&aEnabled")
: ChatUtil.translateAlternateColorCodes('&', "&cDisabled"))));
: ChatUtil.translateAlternateColorCodes('&', "&cDisabled")));
}
}
@KBSyncEventHandler
public void onConfigReload(ConfigReloadEvent event) {
updateConfigValues();
}
private void updateConfigValues() {
ConfigWrapper config = configManager.getConfigWrapper();
this.globalStatusMessage = config.getString("global_status_message", "&eGlobal KnockbackSync status: ");
this.playerStatusMessage = config.getString("player_status_message", "&e%player%'s KnockbackSync status: ");
this.playerDisabledGlobalMessage = config.getString("player_disabled_global_message", "&cDisabled (Global toggle is off)");
}
}
@@ -1,7 +1,8 @@
package me.caseload.knockbacksync.command.subcommand;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.command.generic.BuilderCommand;
import me.caseload.knockbacksync.command.generic.PlayerSelector;
import me.caseload.knockbacksync.event.events.ToggleOnOffEvent;
import me.caseload.knockbacksync.manager.ConfigManager;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.permission.PermissionChecker;
@@ -10,7 +11,6 @@ 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;
@@ -18,8 +18,8 @@ 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 ConfigManager configManager = Base.INSTANCE.getConfigManager();
private static final PermissionChecker permissionChecker = Base.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";
@@ -30,11 +30,11 @@ public class ToggleCommand implements BuilderCommand {
manager.command(
manager.commandBuilder("knockbacksync", "kbsync", "kbs")
.literal("toggle")
.optional("target", KnockbackSyncBase.INSTANCE.getPlayerSelectorParser().descriptor())
.optional("target", Base.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_SELF_PERMISSION, false)
|| sender.hasPermission(TOGGLE_OTHER_PERMISSION, false);
};
@@ -74,10 +74,16 @@ public class ToggleCommand implements BuilderCommand {
private static void toggleGlobalKnockback(ConfigManager configManager, Sender sender) {
boolean toggledState = !configManager.isToggled();
ToggleOnOffEvent toggleOnOffEvent = new ToggleOnOffEvent(toggledState);
toggleOnOffEvent.post();
if (toggleOnOffEvent.isCancelled())
return;
toggledState = toggleOnOffEvent.getStatus();
configManager.setToggled(toggledState);
KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper().set("enabled", toggledState);
KnockbackSyncBase.INSTANCE.getConfigManager().saveConfig();
Base.INSTANCE.getConfigManager().getConfigWrapper().set("enabled", toggledState);
Base.INSTANCE.getConfigManager().saveConfig();
String message = ChatUtil.translateAlternateColorCodes('&',
toggledState ? configManager.getEnableMessage() : configManager.getDisableMessage()
@@ -101,7 +107,7 @@ public class ToggleCommand implements BuilderCommand {
if (hasPlayerData)
PlayerDataManager.removePlayerData(uuid);
else {
PlayerDataManager.addPlayerData(uuid, new PlayerData(KnockbackSyncBase.INSTANCE.platformServer.getPlayer(uuid)));
PlayerDataManager.addPlayerData(uuid, new PlayerData(Base.INSTANCE.platformServer.getPlayer(uuid)));
}
String message = ChatUtil.translateAlternateColorCodes('&',
@@ -1,8 +1,11 @@
package me.caseload.knockbacksync.command.subcommand;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.ConfigWrapper;
import me.caseload.knockbacksync.command.generic.BuilderCommand;
import me.caseload.knockbacksync.permission.PermissionChecker;
import me.caseload.knockbacksync.event.KBSyncEventHandler;
import me.caseload.knockbacksync.event.events.ConfigReloadEvent;
import me.caseload.knockbacksync.manager.ConfigManager;
import me.caseload.knockbacksync.sender.Sender;
import me.caseload.knockbacksync.util.ChatUtil;
import org.incendo.cloud.CommandManager;
@@ -12,7 +15,14 @@ import java.util.function.Predicate;
public class ToggleOffGroundSubcommand implements BuilderCommand {
@Override
private String enableOffGroundSyncMessage;
private String disableOffGroundSyncMessage;
private boolean offGroundSyncEnabled;
public ToggleOffGroundSubcommand() {
loadConfigSettings();
}
public void register(CommandManager<Sender> manager) {
manager.command(
manager.commandBuilder("knockbacksync", "kbsync", "kbs")
@@ -26,14 +36,24 @@ public class ToggleOffGroundSubcommand implements BuilderCommand {
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();
boolean toggledState = !offGroundSyncEnabled;
Base.INSTANCE.getConfigManager().getConfigWrapper().set("enable_offground_synchronization", toggledState);
Base.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."));
toggledState ? enableOffGroundSyncMessage : disableOffGroundSyncMessage);
commandContext.sender().sendMessage(message);
}));
}
@KBSyncEventHandler
public void onConfigReloadEvent(ConfigReloadEvent event) {
loadConfigSettings();
}
private void loadConfigSettings() {
ConfigWrapper configWrapper = Base.INSTANCE.getConfigManager().getConfigWrapper();
offGroundSyncEnabled = configWrapper.getBoolean("enable_offground_synchronization", false);
enableOffGroundSyncMessage = configWrapper.getString("enable_offground_synchronization_message", "&aSuccessfully enabled offground experiment.");
disableOffGroundSyncMessage = configWrapper.getString("disable_offground_synchronization_message", "&cSuccessfully disabled offground experiment.");
}
}
@@ -0,0 +1,177 @@
package me.caseload.knockbacksync.config;
import lombok.Getter;
import lombok.Setter;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
public class YamlConfiguration {
private final File file;
private final Yaml yaml;
@Getter
@Setter
private Map<String, Object> data;
private String fileContents;
public YamlConfiguration(File file) {
this.file = file;
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
options.setPrettyFlow(true);
options.setWidth(Integer.MAX_VALUE);
this.yaml = new Yaml(options);
}
public void load() throws IOException {
if (!file.exists()) {
data = new LinkedHashMap<>();
return;
}
fileContents = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
data = yaml.load(fileContents);
if (data == null) {
data = new LinkedHashMap<>();
}
}
public void save() throws IOException {
if (!file.exists()) {
file.getParentFile().mkdirs();
file.createNewFile();
}
String dump = yaml.dump(data);
if (fileContents != null) {
Map<String, YamlElement> oldElements = parseYaml(fileContents);
Map<String, YamlElement> newElements = parseYaml(dump);
StringBuilder result = new StringBuilder();
buildOutput(oldElements, newElements, result);
dump = result.toString();
}
try (Writer writer = new OutputStreamWriter(Files.newOutputStream(file.toPath()), StandardCharsets.UTF_8)) {
writer.write(dump);
}
}
private static class YamlElement {
String key;
String value;
int indent;
boolean isComment;
List<YamlElement> children = new ArrayList<>();
YamlElement(String line, int indent) {
this.indent = indent;
if (line.trim().startsWith("#")) {
this.isComment = true;
this.value = line;
} else {
this.isComment = false;
String[] parts = line.split(":", 2);
this.key = parts[0].trim();
this.value = parts.length > 1 ? parts[1].trim() : "";
}
}
}
private Map<String, YamlElement> parseYaml(String content) {
Map<String, YamlElement> elements = new LinkedHashMap<>();
Stack<YamlElement> stack = new Stack<>();
String[] lines = content.split("\n");
for (String line : lines) {
if (line.trim().isEmpty()) continue;
int indent = getIndentation(line);
YamlElement element = new YamlElement(line, indent);
while (!stack.isEmpty() && stack.peek().indent >= indent) {
stack.pop();
}
if (stack.isEmpty()) {
if (!element.isComment) {
elements.put(element.key, element);
} else {
elements.put("comment" + elements.size(), element);
}
} else {
stack.peek().children.add(element);
}
if (!element.isComment && element.value.isEmpty()) {
stack.push(element);
}
}
return elements;
}
private void buildOutput(Map<String, YamlElement> oldElements, Map<String, YamlElement> newElements, StringBuilder result) {
for (YamlElement element : oldElements.values()) {
if (element.isComment) {
result.append(element.value).append("\n");
} else {
YamlElement newElement = newElements.get(element.key);
if (newElement != null) {
String indent = createIndent(element.indent);
result.append(indent).append(element.key).append(":");
if (!newElement.value.isEmpty()) {
result.append(" ").append(newElement.value);
}
result.append("\n");
if (!element.children.isEmpty()) {
Map<String, YamlElement> oldChildren = new LinkedHashMap<>();
Map<String, YamlElement> newChildren = new LinkedHashMap<>();
for (YamlElement child : element.children) {
if (!child.isComment) {
oldChildren.put(child.key, child);
} else {
oldChildren.put("comment" + oldChildren.size(), child);
}
}
for (YamlElement child : newElement.children) {
if (!child.isComment) {
newChildren.put(child.key, child);
}
}
buildOutput(oldChildren, newChildren, result);
}
}
}
}
}
private int getIndentation(String line) {
int indent = 0;
while (indent < line.length() && line.charAt(indent) == ' ') {
indent++;
}
return indent;
}
private String createIndent(int count) {
StringBuilder sb = new StringBuilder(count);
for (int i = 0; i < count; i++) {
sb.append(' ');
}
return sb.toString();
}
}
@@ -0,0 +1,23 @@
package me.caseload.knockbacksync.event;
import lombok.Setter;
import me.caseload.knockbacksync.event.OptimizedEventBus;
public abstract class Event {
@Setter
private static EventBus eventBus;
@Setter
private boolean cancelled = false;
public void post() {
if (eventBus != null) {
eventBus.post(this);
} else {
throw new IllegalStateException("EventBus has not been set");
}
}
public boolean isCancelled() {
return cancelled;
}
}
@@ -0,0 +1,13 @@
package me.caseload.knockbacksync.event;
public interface EventBus {
void registerListeners(Object listener);
void registerStaticListeners(Class<?> clazz);
void unregisterListeners(Object listener);
void unregisterStaticListeners(Class<?> clazz);
void post(Event event);
}
@@ -0,0 +1,12 @@
package me.caseload.knockbacksync.event;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface KBSyncEventHandler {
int priority() default 0;
}
@@ -0,0 +1,97 @@
package me.caseload.knockbacksync.event;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class OptimizedEventBus implements EventBus {
private final Map<Class<? extends Event>, List<OptimizedListener>> listenerMap = new HashMap<>();
private final MethodHandles.Lookup lookup = MethodHandles.lookup();
@Override
public void registerListeners(Object listener) {
registerMethods(listener, listener.getClass());
}
@Override
public void registerStaticListeners(Class<?> clazz) {
registerMethods(null, clazz);
}
private void registerMethods(Object instance, Class<?> clazz) {
for (Method method : clazz.getDeclaredMethods()) {
KBSyncEventHandler annotation = method.getAnnotation(KBSyncEventHandler.class);
if (annotation != null && method.getParameterCount() == 1) {
Class<?> eventType = method.getParameterTypes()[0];
if (Event.class.isAssignableFrom(eventType)) {
try {
// Skip instance methods when registering static listeners
if (instance == null && !Modifier.isStatic(method.getModifiers())) {
continue;
}
MethodHandle handle = lookup.unreflect(method);
OptimizedListener optimizedListener = new OptimizedListener(instance, handle, annotation.priority(), method.getDeclaringClass());
listenerMap.computeIfAbsent((Class<? extends Event>) eventType, k -> new ArrayList<>()).add(optimizedListener);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
// Sort listeners by priority
listenerMap.values().forEach(list -> list.sort((a, b) -> Integer.compare(b.priority, a.priority)));
}
@Override
public void unregisterListeners(Object listener) {
listenerMap.values().forEach(list -> list.removeIf(l -> l.instance == listener));
}
@Override
public void unregisterStaticListeners(Class<?> clazz) {
listenerMap.values().forEach(list -> list.removeIf(l -> l.instance == null && l.declaringClass == clazz));
}
@Override
public void post(Event event) {
List<OptimizedListener> listeners = listenerMap.get(event.getClass());
if (listeners != null) {
for (OptimizedListener listener : listeners) {
try {
if (listener.instance != null) {
// Instance method
listener.handle.invoke(listener.instance, event);
} else {
// Static method
listener.handle.invoke(event);
}
if (event.isCancelled()) {
break;
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
}
private static class OptimizedListener {
final Object instance; // null for static methods
final MethodHandle handle;
final int priority;
final Class<?> declaringClass;
OptimizedListener(Object instance, MethodHandle handle, int priority, Class<?> declaringClass) {
this.instance = instance;
this.handle = handle;
this.priority = priority;
this.declaringClass = declaringClass;
}
}
}
@@ -0,0 +1,15 @@
package me.caseload.knockbacksync.event.events;
import lombok.Getter;
import me.caseload.knockbacksync.event.Event;
import me.caseload.knockbacksync.manager.ConfigManager;
@Getter
public class ConfigReloadEvent extends Event {
private final ConfigManager configManager;
public ConfigReloadEvent(ConfigManager configManager) {
this.configManager = configManager;
}
}
@@ -0,0 +1,24 @@
package me.caseload.knockbacksync.event.events;
import me.caseload.knockbacksync.event.Event;
public class ToggleOnOffEvent extends Event {
private boolean newEnabledState;
public ToggleOnOffEvent(boolean newEnabledState) {
this.newEnabledState = newEnabledState;
}
public boolean getStatus() {
return newEnabledState;
}
public void setStatus(boolean newEnabledState) {
this.newEnabledState = newEnabledState;
}
@Override
public void setCancelled(boolean cancelled) {
super.setCancelled(cancelled);
}
}
@@ -1,13 +1,13 @@
package me.caseload.knockbacksync.listener;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.player.PlatformPlayer;
import me.caseload.knockbacksync.player.PlayerData;
public abstract class PlayerDamageListener {
public void onPlayerDamage(PlatformPlayer victim, PlatformPlayer attacker) {
if (!KnockbackSyncBase.INSTANCE.getConfigManager().isToggled())
if (!Base.INSTANCE.getConfigManager().isToggled())
return;
PlayerData playerData = PlayerDataManager.getPlayerData(victim.getUUID());
@@ -18,7 +18,7 @@ public abstract class PlayerDamageListener {
playerData.setLastDamageTicks(victim.getNoDamageTicks());
playerData.updateCombat();
if (!KnockbackSyncBase.INSTANCE.getConfigManager().isRunnableEnabled())
if (!Base.INSTANCE.getConfigManager().isRunnableEnabled())
playerData.sendPing(false);
}
}
@@ -1,6 +1,6 @@
package me.caseload.knockbacksync.listener;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.player.PlatformPlayer;
import me.caseload.knockbacksync.player.PlayerData;
@@ -13,7 +13,7 @@ public abstract class PlayerJoinQuitListener {
PlayerDataManager.addPlayerData(player.getUuid(), player);
PlatformPlayer platformPlayer = player.getPlatformPlayer();
if (KnockbackSyncBase.INSTANCE.getConfigManager().isUpdateAvailable() && KnockbackSyncBase.INSTANCE.getConfigManager().isNotifyUpdate() && KnockbackSyncBase.INSTANCE.getPermissionChecker().hasPermission(platformPlayer, "knockbacksync.update"))
if (Base.INSTANCE.getConfigManager().isUpdateAvailable() && Base.INSTANCE.getConfigManager().isNotifyUpdate() && Base.INSTANCE.getPermissionChecker().hasPermission(platformPlayer, "knockbacksync.update"))
platformPlayer.sendMessage(ChatUtil.translateAlternateColorCodes(
'&',
"&6An updated version of &eKnockbackSync &6is now available for download at: &bhttps://github.com/CASELOAD7000/knockback-sync/releases/latest"
@@ -26,7 +26,7 @@ public abstract class PlayerJoinQuitListener {
return;
if (playerData.isInCombat())
playerData.quitCombat(true);
playerData.quitCombat();
PlayerDataManager.removePlayerData(uuid);
}
@@ -1,7 +1,9 @@
package me.caseload.knockbacksync.listener;
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 me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.player.PlatformPlayer;
import me.caseload.knockbacksync.player.PlayerData;
@@ -9,24 +11,40 @@ import me.caseload.knockbacksync.player.PlayerData;
public abstract class PlayerKnockbackListener {
public void onPlayerVelocity(PlatformPlayer victim, Vector3d velocity) {
if (!KnockbackSyncBase.INSTANCE.getConfigManager().isToggled())
if (!Base.INSTANCE.getConfigManager().isToggled())
return;
PlayerData playerData = PlayerDataManager.getPlayerData(victim.getUUID());
if (playerData == null)
PlayerData victimPlayerData = PlayerDataManager.getPlayerData(victim.getUUID());
if (victimPlayerData == null)
return;
Integer damageTicks = playerData.getLastDamageTicks();
/* Integer damageTicks = playerData.getLastDamageTicks();
if (damageTicks != null && damageTicks > 8)
return;*/
if (victimPlayerData.getNotNullPing() < PlayerData.PING_OFFSET)
return;
Double verticalVelocity = playerData.getVerticalVelocity();
if (verticalVelocity == null || !playerData.isOnGround(velocity.getY()))
double distanceToGround = victimPlayerData.getDistanceToGround();
if (distanceToGround <= 0)
return; // minecraft already does the work for us
WrappedBlockState blockState = victim.getWorld().getBlockStateAt(victim.getLocation());
if (victim.isGliding() ||
blockState.getType() == StateTypes.WATER ||
blockState.getType() == StateTypes.LAVA ||
blockState.getType() == StateTypes.COBWEB ||
blockState.getType() == StateTypes.SCAFFOLDING)
return;
// Since we're already changing types do we need to use withY to get a new object
// Or can we just go velcoity.y = verticalVelocity ?
Vector3d adjustedVelocity = velocity.withY(verticalVelocity);
victim.setVelocity(adjustedVelocity); // Use PlatformPlayer's setVelocity
Vector3d adjustedVelocity;
if (victimPlayerData.isOnGroundClientSide(velocity.getY(), distanceToGround))
adjustedVelocity = velocity.withY(victimPlayerData.getVerticalVelocity()); // Should be impossible to produce a NPE in this context
else if (victimPlayerData.isOffGroundSyncEnabled())
adjustedVelocity = velocity.withY(victimPlayerData.getCompensatedOffGroundVelocity());
else
return;
victim.setVelocity(adjustedVelocity);
}
}
@@ -6,6 +6,7 @@ import com.github.retrooper.packetevents.protocol.attribute.Attributes;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUpdateAttributes;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.player.PlayerData;
import me.caseload.knockbacksync.util.MathUtil;
@@ -29,6 +30,8 @@ public class AttributeChangeListener extends PacketListenerAbstract {
@Override
public void onPacketSend(PacketSendEvent event) {
if (!Base.INSTANCE.getConfigManager().isToggled()) return;
if (event.getPacketType() == PacketType.Play.Server.UPDATE_ATTRIBUTES) {
WrapperPlayServerUpdateAttributes packet = new WrapperPlayServerUpdateAttributes(event);
@@ -11,7 +11,7 @@ import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientWi
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.Base;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.player.PlayerData;
import me.caseload.knockbacksync.util.data.Pair;
@@ -19,9 +19,11 @@ 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) {
if (!Base.INSTANCE.getConfigManager().isToggled()) return;
PacketTypeCommon packetType = event.getPacketType();
if (packetType.equals(PacketType.Play.Server.KEEP_ALIVE)) {
WrapperPlayServerKeepAlive keepAlive = new WrapperPlayServerKeepAlive(event);
@@ -52,8 +54,9 @@ public class PingReceiveListener extends PacketListenerAbstract {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (!KnockbackSyncBase.INSTANCE.getConfigManager().isToggled()) return;
// Do not immediately return if KB sync is disabled. This is because if we send a packet, disable the plugin
// And then receive a response we have to ensure that if the packet was sent by us we cancel it
// otherwise the server will likely kick the player
PacketTypeCommon packetType = event.getPacketType();
if (packetType == PacketType.Play.Client.KEEP_ALIVE) {
@@ -84,16 +87,6 @@ public class PingReceiveListener extends PacketListenerAbstract {
}
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
@@ -103,5 +96,17 @@ public class PingReceiveListener extends PacketListenerAbstract {
// To stop MC from processing it and kicking the player
event.setCancelled(true);
}
if (!Base.INSTANCE.getConfigManager().isToggled()) return;
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);
}
}
@@ -1,17 +1,17 @@
package me.caseload.knockbacksync.manager;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import lombok.Getter;
import lombok.Setter;
import me.caseload.knockbacksync.ConfigWrapper;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.Platform;
import me.caseload.knockbacksync.config.YamlConfiguration;
import me.caseload.knockbacksync.runnable.PingRunnable;
import me.caseload.knockbacksync.scheduler.AbstractTaskHandle;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
@@ -19,7 +19,7 @@ import java.util.Map;
@Setter
public class ConfigManager {
public static final long CONFIG_VERSION = 2;
public static final long CONFIG_VERSION = 4;
private boolean toggled;
private boolean runnableEnabled;
@@ -35,19 +35,18 @@ public class ConfigManager {
private String playerEnableMessage;
private String playerDisableMessage;
private String playerIneligibleMessage;
private String reloadMessage;
private AbstractTaskHandle pingTask;
private Map<String, Object> config;
private File configFile;
private ObjectMapper mapper;
private ConfigWrapper configWrapper; // Cache the ConfigWrapper instance
private YamlConfiguration yamlConfig;
public ConfigManager() {
mapper = new ObjectMapper(new YAMLFactory());
KnockbackSyncBase instance = KnockbackSyncBase.INSTANCE;
Base instance = Base.INSTANCE;
configFile = new File(instance.getDataFolder(), "config.yml");
yamlConfig = new YamlConfiguration(configFile);
}
public ConfigWrapper getConfigWrapper() {
@@ -60,9 +59,10 @@ public class ConfigManager {
public void reloadConfig() {
try {
if (!configFile.exists()) {
KnockbackSyncBase.INSTANCE.saveDefaultConfig();
Base.INSTANCE.saveDefaultConfig();
}
config = mapper.readValue(configFile, Map.class);
yamlConfig.load();
config = yamlConfig.getData();
configWrapper = new ConfigWrapper(config);
} catch (IOException e) {
e.printStackTrace();
@@ -73,7 +73,8 @@ public class ConfigManager {
public void saveConfig() {
try {
mapper.writeValue(configFile, config);
yamlConfig.setData(config);
yamlConfig.save();
} catch (IOException e) {
e.printStackTrace();
}
@@ -84,40 +85,41 @@ public class ConfigManager {
reloadConfig();
}
ConfigWrapper config = getConfigWrapper(); // Use cached ConfigWrapper
ConfigWrapper configWrapper = getConfigWrapper(); // Use cached ConfigWrapper
toggled = config.getBoolean("enabled", true);
updateConfig();
toggled = configWrapper.getBoolean("enabled", true);
// Checks to see if the runnable was enabled...
// and if we now want to disable it
boolean newRunnableEnabled = config.getBoolean("runnable.enabled", true);
boolean newRunnableEnabled = configWrapper.getBoolean("runnable.enabled", true);
if (runnableEnabled && newRunnableEnabled && pingTask != null) { // null check for first startup
pingTask.cancel();
}
runnableEnabled = newRunnableEnabled;
runnableInterval = config.getLong("runnable.interval", 5L);
runnableInterval = configWrapper.getLong("runnable.interval", 5L);
if (runnableEnabled) {
long initialDelay = 0L;
long pingTaskRunnableInterval = runnableInterval;
// Folia does not allow 0 ticks of wait time
if (KnockbackSyncBase.INSTANCE.platform == Platform.FOLIA) {
if (Base.INSTANCE.platform == Platform.FOLIA) {
initialDelay = 1L;
pingTaskRunnableInterval = Math.max(pingTaskRunnableInterval, 1L);
}
pingTask = KnockbackSyncBase.INSTANCE.getScheduler().runTaskTimerAsynchronously(new PingRunnable(), initialDelay, pingTaskRunnableInterval);
pingTask = Base.INSTANCE.getScheduler().runTaskTimerAsynchronously(new PingRunnable(), initialDelay, pingTaskRunnableInterval);
}
notifyUpdate = config.getBoolean("notify_updates", true);
combatTimer = config.getLong("runnable.timer", 30L);
spikeThreshold = config.getLong("spike_threshold", 20L);
enableMessage = config.getString("enable_message", "&aSuccessfully enabled KnockbackSync.");
disableMessage = config.getString("disable_message", "&cSuccessfully disabled KnockbackSync.");
playerEnableMessage = config.getString("player_enable_message", "&aSuccessfully enabled KnockbackSync for %player%.");
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.");
notifyUpdate = configWrapper.getBoolean("notify_updates", true);
combatTimer = configWrapper.getLong("runnable.timer", 30L);
spikeThreshold = configWrapper.getLong("spike_threshold", 20L);
enableMessage = configWrapper.getString("enable_message", "&aSuccessfully enabled KnockbackSync.");
disableMessage = configWrapper.getString("disable_message", "&cSuccessfully disabled KnockbackSync.");
playerEnableMessage = configWrapper.getString("player_enable_message", "&aSuccessfully enabled KnockbackSync for %player%.");
playerDisableMessage = configWrapper.getString("player_disable_message", "&cSuccessfully disabled KnockbackSync for %player%.");
playerIneligibleMessage = configWrapper.getString("player_ineligible_message", "&c%player% is ineligible for KnockbackSync. If you believe this is in error, please contact your server administrators.");
}
public void updateConfig() {
@@ -125,16 +127,20 @@ public class ConfigManager {
long oldConfigVersion = oldConfig.getLong("config_version", 0);
if (oldConfigVersion < CONFIG_VERSION) {
// Backup old config
// Backup old config with comments
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");
try {
Files.move(configFile.toPath(), backupFile.toPath());
Base.INSTANCE.getLogger().info("Backed up old config to " + backupFile.getName());
} catch (IOException e) {
Base.INSTANCE.getLogger().warning("Failed to backup old config: " + e.getMessage());
}
// Store old values
Map<String, Object> oldValues = new HashMap<>(config);
// Create new config with default values
KnockbackSyncBase.INSTANCE.saveDefaultConfig();
Base.INSTANCE.saveDefaultConfig();
reloadConfig();
// Transfer existing settings
@@ -147,17 +153,17 @@ public class ConfigManager {
// Save updated config
saveConfig();
KnockbackSyncBase.INSTANCE.getLogger().info("Config updated to version " + CONFIG_VERSION);
Base.INSTANCE.getLogger().info("Config updated to version " + CONFIG_VERSION);
}
}
private void transferAllSettings(ConfigWrapper oldConfig, ConfigWrapper newConfig) {
transferSettingsRecursive(oldConfig, 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;
String fullPath = currentPath.isEmpty() || currentPath.equals(".") ? key : currentPath + "." + key;
if (newConfig.contains(fullPath)) {
Object value = oldConfig.get(fullPath);
if (value instanceof Map) {
@@ -1,5 +1,6 @@
package me.caseload.knockbacksync.manager;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.player.PlayerData;
import me.caseload.knockbacksync.util.FloodgateUtil;
import me.caseload.knockbacksync.util.GeyserUtil;
@@ -18,12 +19,16 @@ public class PlayerDataManager {
}
public static void addPlayerData(@NotNull UUID uuid, @NotNull PlayerData playerData) {
if (!shouldExempt(uuid))
if (!shouldExempt(uuid)) {
playerDataMap.put(uuid, playerData);
Base.INSTANCE.getSimpleEventBus().registerListeners(playerData);
}
}
public static void removePlayerData(@NotNull UUID uuid) {
playerDataMap.remove(uuid);
PlayerData playerData = playerDataMap.remove(uuid);
if (playerData != null)
Base.INSTANCE.getSimpleEventBus().unregisterListeners(playerData);
}
public static boolean containsPlayerData(@NotNull UUID uuid) {
@@ -1,8 +0,0 @@
package me.caseload.knockbacksync.player;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
public interface PlatformBlock {
WrappedBlockState getMaterial();
// Add more methods as needed
}
@@ -1,5 +1,6 @@
package me.caseload.knockbacksync.player;
import com.github.retrooper.packetevents.protocol.world.BoundingBox;
import com.github.retrooper.packetevents.util.Vector3d;
import me.caseload.knockbacksync.world.PlatformWorld;
import org.jetbrains.annotations.NotNull;
@@ -43,5 +44,11 @@ public interface PlatformPlayer {
@Nullable Integer getNoDamageTicks();
void setVelocity(Vector3d adjustedVelocity);
Vector3d getVelocity();
double getJumpPower();
BoundingBox getBoundingBox();
// Add more methods as needed
}
@@ -7,6 +7,7 @@ import com.github.retrooper.packetevents.netty.channel.ChannelHelper;
import com.github.retrooper.packetevents.protocol.ConnectionState;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.protocol.world.BoundingBox;
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;
@@ -16,7 +17,10 @@ import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPi
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWindowConfirmation;
import lombok.Getter;
import lombok.Setter;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.event.KBSyncEventHandler;
import me.caseload.knockbacksync.event.events.ConfigReloadEvent;
import me.caseload.knockbacksync.event.events.ToggleOnOffEvent;
import me.caseload.knockbacksync.manager.CombatManager;
import me.caseload.knockbacksync.scheduler.AbstractTaskHandle;
import me.caseload.knockbacksync.util.MathUtil;
@@ -24,14 +28,11 @@ 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;
@Getter
public class PlayerData {
@@ -43,12 +44,12 @@ public class PlayerData {
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;
private static final short MAIN_THREAD_TRANSACTION_ID = 31407;
private static final short NETTY_THREAD_TRANSACTION_ID = 31408;
static {
try {
switch (KnockbackSyncBase.INSTANCE.platform) {
switch (Base.INSTANCE.platform) {
case BUKKIT:
case FOLIA:
Class<?> bukkitPlayerClass = Class.forName("me.caseload.knockbacksync.player.BukkitPlayer");
@@ -59,7 +60,7 @@ public class PlayerData {
playerField = fabricPlayerClass.getDeclaredField("fabricPlayer");
break;
default:
throw new IllegalStateException("Unexpected platform: " + KnockbackSyncBase.INSTANCE.platform);
throw new IllegalStateException("Unexpected platform: " + Base.INSTANCE.platform);
}
} catch (Exception e) {
e.printStackTrace();
@@ -80,6 +81,7 @@ public class PlayerData {
@Nullable @Setter private Integer lastDamageTicks;
@Setter private double gravityAttribute = 0.08;
@Setter private double knockbackResistanceAttribute = 0.0;
private boolean offGroundSyncEnabled;
public PlayerData(PlatformPlayer platformPlayer) {
this.uuid = platformPlayer.getUUID();
@@ -100,6 +102,16 @@ public class PlayerData {
}
this.user = tempUser;
this.offGroundSyncEnabled = Base.INSTANCE.getConfigManager().getConfigWrapper().getBoolean("enable_offground_synchronization",
true);
}
public double getNotNullPing() {
return ping != null ? ping : platformPlayer.getPing();
}
public double getNotNullPreviousPing() {
return previousPing != null ? previousPing : platformPlayer.getPing();
}
/**
@@ -109,22 +121,30 @@ public class PlayerData {
*
* @return The compensated ping, with a minimum of 1.
*/
public double getEstimatedPing() {
double currentPing = (ping != null) ? ping : platformPlayer.getPing();
double lastPing = (previousPing != null) ? previousPing : platformPlayer.getPing();
double ping = (currentPing - lastPing > KnockbackSyncBase.INSTANCE.getConfigManager().getSpikeThreshold()) ? lastPing : currentPing;
public double getCompensatedPing() {
double ping = getNotNullPing();
double previousPing = getNotNullPreviousPing();
double spikeCompensatedPing = (ping - previousPing > Base.INSTANCE.getConfigManager().getSpikeThreshold()) ? previousPing : ping;
return Math.max(1, ping - PING_OFFSET);
return Math.max(1, spikeCompensatedPing - PING_OFFSET);
}
// public boolean isKeepAliveIDOurs(long id) {
// return id == lastKeepAliveID;
// }
public int getCompensatedTicks() {
return (int) Math.ceil(getCompensatedPing() * TICK_RATE / 1000); // Multiply ping by seconds per tick
}
public int getTicks() {
return (int) Math.ceil(getNotNullPing() * TICK_RATE / 1000); // Multiply ping by seconds per tick
}
public boolean isSpike() {
return (getNotNullPing() - getNotNullPreviousPing()) > Base.INSTANCE.getConfigManager().getSpikeThreshold();
}
public void sendPing(boolean async) {
if (user == null || user.getEncoderState() != ConnectionState.PLAY) return;
String pingStrategy = KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper().getString("ping_strategy", "KEEPALIVE");
String pingStrategy = Base.INSTANCE.getConfigManager().getConfigWrapper().getString("ping_strategy", "KEEPALIVE");
switch (pingStrategy) {
case "KEEPALIVE":
long keepAliveID = async ? NETTY_THREAD_TRANSACTION_ID : MAIN_THREAD_TRANSACTION_ID;
@@ -176,32 +196,37 @@ public class PlayerData {
* @param verticalVelocity The Player's current vertical velocity.
* @return <code>true</code> if the Player is on the ground; <code>false</code> otherwise.
*/
public boolean isOnGround(double verticalVelocity) {
WrappedBlockState blockState = platformPlayer.getWorld().getBlockStateAt(platformPlayer.getLocation());
if (platformPlayer.isGliding() ||
blockState.getType() == StateTypes.WATER ||
blockState.getType() == StateTypes.LAVA ||
blockState.getType() == StateTypes.COBWEB ||
blockState.getType() == StateTypes.SCAFFOLDING) {
return false;
}
if (ping == null || ping < PING_OFFSET)
return false;
double gDist = getDistanceToGround();
if (gDist <= 0)
return false; // prevent player from taking adjusted knockback when on ground serverside
public boolean isOnGroundClientSide(double verticalVelocity, double distanceToGround) {
int tMax = verticalVelocity > 0 ? MathUtil.calculateTimeToMaxVelocity(verticalVelocity, gravityAttribute) : 0;
if (tMax == -1)
return false; // reached the max tick limit, not safe to predict
double mH = verticalVelocity > 0 ? MathUtil.calculateDistanceTraveled(verticalVelocity, tMax, gravityAttribute) : 0;
int tFall = MathUtil.calculateFallTime(verticalVelocity, mH + gDist, gravityAttribute);
int tFall = MathUtil.calculateFallTime(verticalVelocity, mH + distanceToGround, gravityAttribute);
if (tFall == -1)
return false; // reached the max tick limit, not safe to predict
return getEstimatedPing() >= tMax + tFall / TICK_RATE * 1000 && gDist <= 1.3;
return (tMax + tFall) - getCompensatedTicks() <= 0 && distanceToGround <= 1.3;
}
/**
* Gets whether or not offGroundSynchronization is enabled for the player.
* @return <code>true</code> if enabled; <code>false</code> otherwise.
*/
public boolean isOffGroundSyncEnabled() {
// TODO, per-player offground sync toggles?
return offGroundSyncEnabled;
}
/**
* Gets the compensated off-ground velocity.
* This is used to make knockback feel more accurate off-ground.
*
* @return Compensated Y axis velocity
*/
public double getCompensatedOffGroundVelocity() {
return MathUtil.getCompensatedVerticalVelocity(platformPlayer.getVelocity().getY(), getGravityAttribute(), getTicks());
}
/**
@@ -218,10 +243,10 @@ public class PlayerData {
for (Vector3d corner : getBBCorners()) {
RayTraceResult result = world.rayTraceBlocks(corner, new Vector3d(0, -1, 0), 5, FluidHandling.NONE, true);
if (result == null || result.getHitPosition() == null)
if (result == null || result.getHitBlock() == null)
continue;
collisionDist = Math.min(collisionDist, corner.getY() - result.getHitPosition().getY());
collisionDist = Math.min(collisionDist, corner.getY() - result.getHitBlockPosition().getY());
}
return collisionDist - 1;
@@ -233,16 +258,16 @@ public class PlayerData {
* @return An array of locations representing the corners of the bounding box.
*/
private Vector3d[] getBBCorners() {
Vector3d playerPos = platformPlayer.getLocation();
double width = 0.6; // typical player width
// double height = 1.8; // typical player height
double adjustment = 0.01;
BoundingBox boundingBox = platformPlayer.getBoundingBox();
Vector3d location = platformPlayer.getLocation();
return new Vector3d[]{
new Vector3d(playerPos.getX() - width / 2 + adjustment, playerPos.getY(), playerPos.getZ() - width / 2 + adjustment),
new Vector3d(playerPos.getX() - width / 2 + adjustment, playerPos.getY(), playerPos.getZ() + width / 2 - adjustment),
new Vector3d(playerPos.getX() + width / 2 - adjustment, playerPos.getY(), playerPos.getZ() - width / 2 + adjustment),
new Vector3d(playerPos.getX() + width / 2 - adjustment, playerPos.getY(), playerPos.getZ() + width / 2 - adjustment)
double adjustment = 0.01; // To ensure the bounding box isn't clipping inside a wall
return new Vector3d[] {
new Vector3d(boundingBox.getMinX() + adjustment, location.getY(), boundingBox.getMinZ() + adjustment),
new Vector3d(boundingBox.getMinX() + adjustment, location.getY(), boundingBox.getMaxZ() - adjustment),
new Vector3d(boundingBox.getMaxX() - adjustment, location.getY(), boundingBox.getMinZ() + adjustment),
new Vector3d(boundingBox.getMaxX() - adjustment, location.getY(), boundingBox.getMaxZ() - adjustment)
};
}
@@ -258,7 +283,6 @@ public class PlayerData {
if (!attacker.isSprinting()) {
yAxis = 0.36080000519752503;
// double knockbackResistance = knockbackResistanceAttribute;
double resistanceFactor = 0.04000000119 * knockbackResistanceAttribute * 10;
yAxis -= resistanceFactor;
}
@@ -270,19 +294,6 @@ public class PlayerData {
return yAxis;
}
// might need soon
// public double calculateJumpVelocity() {
// double jumpVelocity = 0.42;
//
// PotionEffect jumpEffect = player.getPotionEffect(PotionEffectType.JUMP);
// if (jumpEffect != null) {
// int amplifier = jumpEffect.getAmplifier();
// jumpVelocity += (amplifier + 1) * 0.1F;
// }
//
// return jumpVelocity;
// }
public boolean isInCombat() {
return combatTask != null;
}
@@ -295,18 +306,16 @@ public class PlayerData {
CombatManager.addPlayer(uuid);
}
public void quitCombat(boolean cancelTask) {
if (cancelTask)
combatTask.cancel();
public void quitCombat() {
combatTask.cancel(); // should do nothing
combatTask = null;
CombatManager.removePlayer(uuid);
}
@NotNull
private AbstractTaskHandle newCombatTask() {
return KnockbackSyncBase.INSTANCE.getScheduler().runTaskLaterAsynchronously(
() -> quitCombat(false), KnockbackSyncBase.INSTANCE.getConfigManager().getCombatTimer());
return Base.INSTANCE.getScheduler().runTaskLaterAsynchronously(
this::quitCombat, Base.INSTANCE.getConfigManager().getCombatTimer());
}
public ClientVersion getClientVersion() {
@@ -327,4 +336,18 @@ public class PlayerData {
public boolean didWeSendThatPacket(long receivedId) {
return receivedId == NETTY_THREAD_TRANSACTION_ID || receivedId == MAIN_THREAD_TRANSACTION_ID;
}
@KBSyncEventHandler
public void onToggledEvent(ToggleOnOffEvent event) {
if (event.getStatus() == false) {
transactionsSent.clear();
keepaliveMap.clear();
}
}
@KBSyncEventHandler
public void onConfigReloadEvent(ConfigReloadEvent event) {
this.offGroundSyncEnabled = Base.INSTANCE.getConfigManager().getConfigWrapper().getBoolean("enable_offground_synchronization",
true);
}
}
@@ -1,6 +1,6 @@
package me.caseload.knockbacksync.runnable;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.manager.CombatManager;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.player.PlayerData;
@@ -11,7 +11,7 @@ public class PingRunnable implements Runnable {
@Override
public void run() {
if (!KnockbackSyncBase.INSTANCE.getConfigManager().isToggled())
if (!Base.INSTANCE.getConfigManager().isToggled())
return;
for (UUID uuid : CombatManager.getPlayers()) {
@@ -1,6 +1,6 @@
package me.caseload.knockbacksync.sender;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import java.util.UUID;
@@ -10,7 +10,7 @@ import java.util.UUID;
* @param <T> the command sender type
*/
public final class AbstractSender<T> implements Sender {
private final KnockbackSyncBase plugin;
private final Base plugin;
private final SenderFactory<?, T> factory;
private final T sender;
@@ -18,7 +18,7 @@ public final class AbstractSender<T> implements Sender {
private final String name;
private final boolean isConsole;
AbstractSender(KnockbackSyncBase plugin, SenderFactory<?, T> factory, T sender) {
AbstractSender(Base plugin, SenderFactory<?, T> factory, T sender) {
this.plugin = plugin;
this.factory = factory;
this.sender = sender;
@@ -28,7 +28,7 @@ public final class AbstractSender<T> implements Sender {
}
@Override
public KnockbackSyncBase getPlugin() {
public Base getPlugin() {
return this.plugin;
}
@@ -1,6 +1,6 @@
package me.caseload.knockbacksync.sender;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import java.util.UUID;
@@ -23,7 +23,7 @@ public interface Sender {
*
* @return the plugin
*/
KnockbackSyncBase getPlugin();
Base getPlugin();
/**
* Gets the sender's username
@@ -1,6 +1,6 @@
package me.caseload.knockbacksync.sender;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import java.util.Objects;
import java.util.UUID;
@@ -11,7 +11,7 @@ import java.util.UUID;
* @param <P> the plugin type
* @param <T> the command sender type
*/
public abstract class SenderFactory<P extends KnockbackSyncBase, T> implements AutoCloseable {
public abstract class SenderFactory<P extends Base, T> implements AutoCloseable {
private final P plugin;
public SenderFactory(P plugin) {
@@ -2,7 +2,8 @@ package me.caseload.knockbacksync.stats.custom;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.common.BuildConfig;
import me.caseload.knockbacksync.stats.SimplePie;
import org.kohsuke.github.GHAsset;
import org.kohsuke.github.GHRelease;
@@ -18,8 +19,7 @@ public class BuildTypePie extends SimplePie {
private static final String RELEASES_FILE = "releases.txt";
private static final String DEV_BUILDS_FILE = "dev-builds.txt";
private static final File dataFolder = KnockbackSyncBase.INSTANCE.getDataFolder();
public static URL jarUrl;
private static final File dataFolder = Base.INSTANCE.getDataFolder();
private static String cachedBuildType = null;
public BuildTypePie() {
@@ -35,7 +35,7 @@ public class BuildTypePie extends SimplePie {
private static String calculateBuildType() {
try {
String currentHash = KnockbackSyncBase.INSTANCE.getPluginJarHashProvider().getPluginJarHash();
String currentHash = Base.INSTANCE.getPluginJarHashProvider().getPluginJarHash();
downloadBuildFiles();
if (isHashInFile(currentHash, new File(dataFolder, RELEASES_FILE))) {
@@ -53,12 +53,12 @@ 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(BuildConfig.GITHUB_REPO)
.getLatestRelease();
List<GHAsset> assets = latestRelease.listAssets().toList();
for (GHAsset asset : assets) {
if (asset.getName().equals(RELEASES_FILE) || asset.getName().equals(DEV_BUILDS_FILE)) {
KnockbackSyncBase.INSTANCE.getLogger().info("Downloading: " + asset.getName());
Base.INSTANCE.getLogger().info("Downloading: " + asset.getName());
String jsonContent = readStringFromURL(asset.getUrl().toString());
JsonObject jsonObject = JsonParser.parseString(jsonContent).getAsJsonObject();
@@ -72,7 +72,7 @@ public class BuildTypePie extends SimplePie {
}
}
KnockbackSyncBase.INSTANCE.getLogger().info("Downloaded: " + asset.getName());
Base.INSTANCE.getLogger().info("Downloaded: " + asset.getName());
}
}
}
@@ -1,6 +1,6 @@
package me.caseload.knockbacksync.stats.custom;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.player.PlatformPlayer;
import me.caseload.knockbacksync.player.PlayerData;
@@ -15,7 +15,7 @@ public class PlayerVersionsPie extends AdvancedPie {
public PlayerVersionsPie() {
super("player_version", () -> {
Map<String, Integer> valueMap = new HashMap<>();
for (PlatformPlayer player : KnockbackSyncBase.INSTANCE.platformServer.getOnlinePlayers()) {
for (PlatformPlayer player : Base.INSTANCE.platformServer.getOnlinePlayers()) {
PlayerData playerData = PlayerDataManager.getPlayerData(player.getUUID());
valueMap.put(playerData.getClientVersion().toString(), valueMap.getOrDefault(playerData.getClientVersion().toString(), 0) + 1);
}
@@ -44,12 +44,11 @@ public class ChatUtil {
noun = playerData.getPlatformPlayer().getName() + "'s";
}
String rawReturnString = null;
if (playerData.getPing() == null) {
rawReturnString = String.format("Pong not received. %s estimated ping is &b" + playerData.getPlatformPlayer().getPing() + "&rms.", noun);
rawReturnString = String.format("Accurate ping unavailable. %s estimated ping is &b" + playerData.getPlatformPlayer().getPing() + "&rms.", noun);
} else {
rawReturnString = String.format("%s last ping packet took &b %.3f&rms. Jitter: &b%.3f&rms.", noun, playerData.getPing(), playerData.getJitter());
rawReturnString = String.format("%s real ping is &b%.3f&rms. Jitter: &b%.3f&rms. Spike: &b%s&r. Compensated ping: &b%.3f&rms.", noun, playerData.getPing(), playerData.getJitter(), playerData.isSpike(), playerData.getCompensatedPing());
}
return ChatUtil.translateAlternateColorCodes('&', rawReturnString);
}
@@ -2,7 +2,7 @@ package me.caseload.knockbacksync.util;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.util.reflection.Reflection;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -19,7 +19,7 @@ public class GeyserUtil {
public static boolean isGeyserPlayer(UUID uuid) {
if (!CHECKED_FOR_GEYSER) {
try {
switch (KnockbackSyncBase.INSTANCE.platform) {
switch (Base.INSTANCE.platform) {
case BUKKIT:
case FOLIA:
ClassLoader classLoader = PacketEvents.getAPI().getPlugin().getClass().getClassLoader();
@@ -40,7 +40,7 @@ public class GeyserUtil {
if (GEYSER_PRESENT) {
if (GEYSER_API_CLASS == null) {
try {
switch (KnockbackSyncBase.INSTANCE.platform) {
switch (Base.INSTANCE.platform) {
case BUKKIT:
case FOLIA:
ClassLoader classLoader = PacketEvents.getAPI().getPlugin().getClass().getClassLoader();
@@ -1,10 +1,24 @@
package me.caseload.knockbacksync.util;
import me.caseload.knockbacksync.player.PlayerData;
import static me.caseload.knockbacksync.player.PlayerData.TICK_RATE;
public class MathUtil {
private static final double TERMINAL_VELOCITY = 3.92;
private static final double MULTIPLIER = 0.98;
private static final int MAX_TICKS = 20;
private static final int MAX_TICKS = 30;
public static double getCompensatedVerticalVelocity(double velocity, double acceleration, int ticks) {
while (ticks > 0) {
velocity -= acceleration;
velocity *= MULTIPLIER;
ticks--;
}
return velocity;
}
public static double calculateDistanceTraveled(double velocity, int time, double acceleration) {
double totalDistance = 0;
@@ -36,17 +50,20 @@ public class MathUtil {
return ticks;
}
public static int calculateTimeToMaxVelocity(double targetVerticalVelocity, double acceleration) {
double a = -acceleration * MULTIPLIER;
double b = acceleration + TERMINAL_VELOCITY * MULTIPLIER;
double c = -2 * targetVerticalVelocity;
public static int calculateTimeToMaxVelocity(double velocity, double acceleration) {
int ticks = 0;
double discriminant = b * b - 4 * a * c;
if (discriminant < 0)
return 0;
while (velocity > 0) {
if (ticks > MAX_TICKS)
return -1;
double positiveRoot = (-b + Math.sqrt(discriminant)) / (2 * a);
return (int) Math.ceil(positiveRoot * 20);
velocity -= acceleration;
velocity = Math.min(velocity, TERMINAL_VELOCITY);
velocity *= MULTIPLIER;
ticks++;
}
return ticks;
}
public static double clamp(double num, double min, double max) {
+148 -7
View File
@@ -3,7 +3,7 @@
#########################################
# Do not modify!
config-version: 2
config_version: 5
# Plugin enabled state
# Toggleable using /knockbacksync toggle
@@ -18,7 +18,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.
@@ -38,17 +38,158 @@ spike_threshold: 20
# 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
# Whether to try to detect when player is offground and apply less kb
enable_offground_synchronization: true
enable_message: "&aSuccessfully enabled KnockbackSync."
disable_message: "&cSuccessfully disabled KnockbackSync."
player_enable_message: "&aSuccessfully enabled KnockbackSync for %player%."
player_disable_message: "&aSuccessfully &cdisabled &aKnockbackSync for %player%."
player_ineligible_message: "&c%player% is ineligible for KnockbackSync. If you believe this is an error, please open an issue on the github page."
player_ineligible_message: '&c%player% is ineligible for KnockbackSync. If you believe this is in error, please contact your server administrators.'
reload_message: "&aSuccessfully reloaded KnockbackSync config."
global_status_message: "&eGlobal KnockbackSync status: "
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."
enable_offground_synchronization_message: "&aSuccessfully enabled offground synchronization."
disable_offground_synchronization_message: "&cSuccessfully disabled offground synchronization."
# This is the rate the server will use for (amongst other things) sending packets about entities to players
# In other words it defines how often clients get updated velocity for other entities in the world
# Note that certain events mark velocity as dirty and causes it to be immediately updated for 1 tick regardless of the tick interval
# These include but are not limited to jumping, taking knockback, swimming, and a target entity being affected by a special mob attack
# Such as the enderdragon's huge knockback
# If an entity below doesn't exist in the version of the game your server is running in, changing the config for it will do nothing
# Currently only modifying the player's tick interval is supported on Bukkit/Spigot/Paper + Folia
# Modifying the tick interval of any entity is supported on Fabric.
entity_tick_intervals:
player: 2 # This is 2 by default in vanilla and paper, change to 1 to test out the new feature
allay: 2
area_effect_cloud: Integer.MAX_VALUE
armadillo: 3
armor_stand: 3
arrow: 20
axolotl: 3
bat: 3
bee: 3
blaze: 3
block_display: 1
boat: 3
bogged: 3
breeze: 3
breeze_wind_charge: 10
camel: 3
cat: 3
cave_spider: 3
chest_boat: 3
chest_minecart: 3
chicken: 3
cod: 3
command_block_minecart: 3
cow: 3
creeper: 3
dolphin: 3
donkey: 3
dragon_fireball: 10
drowned: 3
egg: 10
elder_guardian: 3
end_crystal: Integer.MAX_VALUE
ender_dragon: 3
ender_pearl: 10
enderman: 3
endermite: 3
evoker: 3
evoker_fangs: 2
experience_bottle: 10
experience_orb: 20
eye_of_ender: 4
falling_block: 20
firework_rocket: 10
fox: 3
frog: 3
furnace_minecart: 3
ghast: 3
giant: 3
glow_item_frame: Integer.MAX_VALUE
glow_squid: 3
goat: 3
guardian: 3
hoglin: 3
hopper_minecart: 3
horse: 3
husk: 3
illusioner: 3
interaction: 3
iron_golem: 3
item: 20
item_display: 1
item_frame: Integer.MAX_VALUE
ominous_item_spawner: 3
fireball: 10
leash_knot: Integer.MAX_VALUE
lightning_bolt: Integer.MAX_VALUE
llama: 3
llama_spit: 10
magma_cube: 3
marker: 3
minecart: 3
mooshroom: 3
mule: 3
ocelot: 3
painting: Integer.MAX_VALUE
panda: 3
parrot: 3
phantom: 3
pig: 3
piglin: 3
piglin_brute: 3
pillager: 3
polar_bear: 3
potion: 10
pufferfish: 3
rabbit: 3
ravager: 3
salmon: 3
sheep: 3
shulker: 3
shulker_bullet: 3
silverfish: 3
skeleton: 3
skeleton_horse: 3
slime: 3
small_fireball: 10
sniffer: 3
snow_golem: 3
snowball: 10
spawner_minecart: 3
spectral_arrow: 20
spider: 3
squid: 3
stray: 3
strider: 3
tadpole: 3
text_display: 1
tnt: 10
tnt_minecart: 3
trader_llama: 3
trident: 20
tropical_fish: 3
turtle: 3
vex: 3
villager: 3
vindicator: 3
wandering_trader: 3
warden: 3
wind_charge: 10
witch: 3
wither: 3
wither_skeleton: 3
wither_skull: 10
wolf: 3
zoglin: 3
zombie: 3
zombie_horse: 3
zombie_villager: 3
zombified_piglin: 3
fishing_bobber: 5
+14 -4
View File
@@ -3,6 +3,10 @@ plugins {
id("com.gradleup.shadow")
}
loom {
accessWidenerPath = file("src/main/resources/knockbacksync.accesswidener")
}
base {
archivesName.set("${rootProject.property("archives_base_name")}-fabric")
}
@@ -38,12 +42,18 @@ dependencies {
modImplementation("net.fabricmc.fabric-api:fabric-api:${rootProject.property("fabric_version")}")
include(modImplementation("me.lucko:fabric-permissions-api:0.3.1")!!)
include(modImplementation("com.github.retrooper:packetevents-fabric:2.5.8-SNAPSHOT")!!)
include(modImplementation("com.github.retrooper:packetevents-fabric:2.5.0")!!)
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("org.yaml:snakeyaml:2.0")!!)
include(implementation("org.kohsuke:github-api:1.326")!!)
// Required for org.kohsuke.github
include(implementation("com.fasterxml.jackson.core:jackson-databind:2.17.2")!!)
include(implementation("com.fasterxml.jackson.core:jackson-annotations:2.17.2")!!)
include(implementation("com.fasterxml.jackson.core:jackson-core:2.17.2")!!)
// Not requires in modern Minecraft. May be needed if fabric version is backported to older versions
// include(implementation("org.apache.commons:commons-lang3:3.17.0")!!)
// include(implementation("commons-io:commons-io:2.16.1")!!)
compileOnly("org.geysermc.floodgate:api:2.0-SNAPSHOT")
compileOnly("org.projectlombok:lombok:1.18.34")
@@ -3,11 +3,13 @@ package me.caseload.knockbacksync;
import com.github.retrooper.packetevents.PacketEvents;
import io.github.retrooper.packetevents.factory.fabric.FabricPacketEventsBuilder;
import lombok.Getter;
import me.caseload.knockbacksync.entity.EntityTickManager;
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.mixin.ServerEntityMixin;
import me.caseload.knockbacksync.permission.FabricPermissionChecker;
import me.caseload.knockbacksync.permission.PermissionChecker;
import me.caseload.knockbacksync.scheduler.FabricSchedulerAdapter;
@@ -30,14 +32,14 @@ import java.nio.file.Files;
import java.util.Optional;
import java.util.logging.Logger;
public class KBSyncFabricBase extends KnockbackSyncBase {
public class FabricBase extends Base {
private final Logger logger = Logger.getLogger(KBSyncFabricLoaderMod.class.getName());
private final Logger logger = Logger.getLogger(FabricLoaderMod.class.getName());
private final FabricPermissionChecker permissionChecker = new FabricPermissionChecker();
@Getter
private final FabricSenderFactory fabricSenderFactory = new FabricSenderFactory(this);
public KBSyncFabricBase() {
public FabricBase() {
super.configManager = new ConfigManager();
super.playerSelectorParser = new FabricPlayerSelectorParser<>();
super.commandManager = new FabricServerCommandManager<>(
@@ -81,6 +83,7 @@ public class KBSyncFabricBase extends KnockbackSyncBase {
new FabricPlayerDamageListener().register();
new FabricPlayerKnockbackListener().register();
new FabricTickRateChangeListener().register();
super.simpleEventBus.registerStaticListeners(EntityTickManager.class);
}
@Override
@@ -6,9 +6,9 @@ import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;
import net.minecraft.server.MinecraftServer;
public class KBSyncFabricLoaderMod implements PreLaunchEntrypoint, ModInitializer {
public class FabricLoaderMod implements PreLaunchEntrypoint, ModInitializer {
private final KnockbackSyncBase core = new KBSyncFabricBase();
private final Base core = new FabricBase();
public static MinecraftServer getServer() {
return (MinecraftServer) FabricLoader.getInstance().getGameInstance();
@@ -22,10 +22,6 @@ public class KBSyncFabricLoaderMod implements PreLaunchEntrypoint, ModInitialize
@Override
public void onInitialize() {
core.enable();
core.initializeScheduler();
core.configManager.loadConfig(false);
core.statsManager.init();
core.checkForUpdates();
ServerLifecycleEvents.SERVER_STOPPING.register((server) -> {
core.scheduler.shutdown();
core.statsManager.getMetrics().shutdown();
@@ -0,0 +1,49 @@
package me.caseload.knockbacksync.entity;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.ConfigWrapper;
import me.caseload.knockbacksync.event.events.ConfigReloadEvent;
import me.caseload.knockbacksync.event.KBSyncEventHandler;
import net.minecraft.world.entity.EntityType;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class EntityTickManager {
private static final Map<EntityType<?>, Integer> customTickIntervals = new HashMap<>();
static {
updateTickIntervals(Base.INSTANCE.getConfigManager().getConfigWrapper());
}
@KBSyncEventHandler
public static void updateTickIntervals(ConfigReloadEvent event) {
ConfigWrapper configWrapper = event.getConfigManager().getConfigWrapper();
updateTickIntervals(configWrapper);
// Map<String, Integer> entityIntervals = CustomUpdateIntervals.loadEntityIntervalsFromConfig();
// for (ServerLevel level : FabricLoaderMod.getServer().getAllLevels()) {
// CustomUpdateIntervals.updateIntervals(level, entityIntervals);
// }
}
private static void updateTickIntervals(ConfigWrapper configWrapper) {
customTickIntervals.clear();
for (String entityKey : configWrapper.getKeys("entity_tick_intervals")) {
try {
Optional<EntityType<?>> entityType = EntityType.byString(entityKey.toLowerCase());
if (entityType.isPresent()) {
int interval = configWrapper.getInt("entity_tick_intervals." + entityKey, entityType.get().updateInterval());
customTickIntervals.put(entityType.get(), interval);
}
} catch (IllegalArgumentException e) {
System.err.println("Invalid entity type in config: " + entityKey);
}
}
}
public static int getCustomUpdateInterval(EntityType<?> entityType) {
return customTickIntervals.getOrDefault(entityType, entityType.updateInterval());
}
}
@@ -17,18 +17,6 @@ public class FabricPlayerDamageListener extends PlayerDamageListener {
onPlayerDamage((ServerPlayer) player, entity);
return InteractionResult.PASS;
});
// Sending ping packets on tick if runnable is disabled
// ServerTickEvents.END_SERVER_TICK.register(server -> {
// if (!KnockbackSyncBase.INSTANCE.getConfigManager().isRunnableEnabled()) {
// for (ServerPlayerEntity player : server.getPlayerList()) {
// PlayerData playerData = PlayerDataManager.getPlayerData(player);
// if (playerData != null) {
// playerData.sendPing();
// }
// }
// }
// });
}
private void onPlayerDamage(ServerPlayer attacker, Entity victimEntity) {
@@ -0,0 +1,17 @@
package me.caseload.knockbacksync.mixin;
import me.caseload.knockbacksync.entity.EntityTickManager;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.world.entity.EntityType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(ChunkMap.class)
public abstract class ChunkMapMixin {
@Redirect(method = "addEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/EntityType;updateInterval()I"))
private int getCustomUpdateInterval(EntityType<?> entityType) {
return EntityTickManager.getCustomUpdateInterval(entityType);
}
}
@@ -0,0 +1,86 @@
//package me.caseload.knockbacksync.mixin;
//
//import me.caseload.knockbacksync.ConfigWrapper;
//import me.caseload.knockbacksync.KnockbackSyncBase;
//import me.caseload.knockbacksync.event.ConfigReloadEvent;
//import me.caseload.knockbacksync.event.KBSyncEventHandler;
//import me.caseload.knockbacksync.util.CustomUpdateIntervals;
//import net.minecraft.Util;
//import net.minecraft.server.level.ChunkMap;
//import net.minecraft.server.level.ServerLevel;
//import net.minecraft.server.level.ServerPlayer;
//import net.minecraft.world.entity.Entity;
//import net.minecraft.world.entity.EntityType;
//import net.minecraft.world.entity.boss.EnderDragonPart;
//import org.spongepowered.asm.mixin.Final;
//import org.spongepowered.asm.mixin.Mixin;
//import org.spongepowered.asm.mixin.Shadow;
//import org.spongepowered.asm.mixin.injection.At;
//import org.spongepowered.asm.mixin.injection.Inject;
//import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
//
//import java.util.HashMap;
//import java.util.Map;
//
//@Mixin(ChunkMap.class)
//public class ChunkMapMixin {
//
// @Shadow @Final
// private Map<Integer, ChunkMap.TrackedEntity> entityMap;
// @Shadow @Final private ServerLevel level;
//
// @Inject(method = "addEntity", at = @At("HEAD"), cancellable = true)
// private void onAddEntity(Entity entity, CallbackInfo ci) {
// if (!(entity instanceof EnderDragonPart)) {
// EntityType<?> entityType = entity.getType();
// int trackingRange = entityType.clientTrackingRange() * 16;
//
// if (trackingRange != 0) {
// String entityTypeName = entityType.builtInRegistryHolder().getRegisteredName();
// ConfigWrapper config = KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper();
//
// int defaultInterval = entityType.updateInterval();
// int customInterval = config.getInt("entity_update_intervals." + entityTypeName, defaultInterval);
//
// if (this.entityMap.containsKey(entity.getId())) {
// throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("Entity is already tracked!"));
// } else {
// ChunkMap.TrackedEntity trackedEntity = new ChunkMap.TrackedEntity(entity, trackingRange, customInterval, entityType.trackDeltas());
// this.entityMap.put(entity.getId(), trackedEntity);
// trackedEntity.updatePlayers(this.level.players());
//
// if (entity instanceof ServerPlayer serverPlayer) {
// this.updatePlayerStatus(serverPlayer, true);
//
// for (ChunkMap.TrackedEntity trackedEntity2 : this.entityMap.values()) {
// if (trackedEntity2.entity != serverPlayer) {
// trackedEntity2.updatePlayer(serverPlayer);
// }
// }
// }
// }
// }
// }
//
// ci.cancel();
// }
//
// @Shadow
// private void updatePlayerStatus(ServerPlayer player, boolean track) {
// // This is a shadow method, implementation is provided by Minecraft
// }
//
// @KBSyncEventHandler
// public void onConfigReload(ConfigReloadEvent event) {
// Map<String, Integer> newIntervals = CustomUpdateIntervals.loadEntityIntervalsFromConfig();
//
// for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
// Entity entity = trackedEntity.entity;
// String entityTypeName = entity.getType().builtInRegistryHolder().getRegisteredName();
// int newInterval = newIntervals.getOrDefault(entityTypeName, entity.getType().updateInterval());
//
// // Assuming TrackedEntity has a method to update its update interval
// trackedEntity.setUpdateInterval(newInterval);
// }
// }
//}
@@ -16,11 +16,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerEntity.class)
public class ServerEntityMixin {
@Shadow
@Final
private Entity entity;
// Velocity event
@Inject(method = "sendChanges", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/Entity;hurtMarked:Z", ordinal = 1), cancellable = true)
private void onSendChanges(CallbackInfo ci) {
if (this.entity.hurtMarked && this.entity instanceof ServerPlayer player) {
@@ -37,4 +37,23 @@ public class ServerEntityMixin {
}
}
}
// Custom tick intervals
// @Shadow
// private int updateInterval;
// @Accessor("entity")
// Entity getEntity() {
// return null;
// }
// @Invoker("sendChanges")
// void invokeSendChanges() {
//
// }
// @Override
// public void setUpdateInterval(int interval) {
// this.updateInterval = interval;
// }
}
@@ -1,6 +1,7 @@
package me.caseload.knockbacksync.mixin;
import me.caseload.knockbacksync.callback.TickRateChangeEvent;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.TickRateManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -0,0 +1,41 @@
//package me.caseload.knockbacksync.mixin;
//
//import me.caseload.knockbacksync.ConfigWrapper;
//import me.caseload.knockbacksync.KnockbackSyncBase;
//import net.minecraft.core.SectionPos;
//import net.minecraft.server.level.ChunkMap;
//import net.minecraft.server.level.ServerEntity;
//import net.minecraft.world.entity.Entity;
//import org.spongepowered.asm.mixin.Mixin;
//import org.spongepowered.asm.mixin.Shadow;
//import org.spongepowered.asm.mixin.gen.Accessor;
//import org.spongepowered.asm.mixin.injection.At;
//import org.spongepowered.asm.mixin.injection.Inject;
//import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
//
//@Mixin(ChunkMap.TrackedEntity.class)
//public class TrackedEntityMixin {
// @Shadow
// ServerEntity serverEntity;
// Entity entity;
// int range;
// SectionPos lastSectionPos;
//
// @Inject(method = "<init>", at = @At("HEAD"))
// private void onInit(ChunkMap outer, Entity entity, int range, int updateInterval, boolean trackDelta, CallbackInfo ci) {
//
// String entityTypeName = entity.getType().builtInRegistryHolder().getRegisteredName();
// ConfigWrapper config = KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper();
// int customInterval = config.getInt("entity_update_intervals." + entityTypeName, updateInterval);
//
// this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, customInterval, trackDelta, this::broadcast);
// this.entity = entity;
// this.range = range;
// this.lastSectionPos = SectionPos.of(entity);
// }
//
//// @Accessor("updateInterval")
//// public void setUpdateInterval(int interval) {
// // This method will be implemented by the mixin system
//// }
//}
@@ -0,0 +1,5 @@
package me.caseload.knockbacksync.mixin;
public interface UpdateIntervalAccessor {
void setUpdateInterval(int interval);
}
@@ -1,5 +1,7 @@
package me.caseload.knockbacksync.player;
import com.github.retrooper.packetevents.protocol.world.BoundingBox;
import com.github.retrooper.packetevents.protocol.world.Location;
import com.github.retrooper.packetevents.util.Vector3d;
import me.caseload.knockbacksync.world.FabricWorld;
import me.caseload.knockbacksync.world.PlatformWorld;
@@ -8,9 +10,12 @@ import net.minecraft.core.registries.Registries;
import net.minecraft.data.registries.VanillaRegistries;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -82,8 +87,7 @@ public class FabricPlayer implements PlatformPlayer {
@Override
public Vector3d getLocation() {
Vec3 pos = fabricPlayer.position();
return new Vector3d(pos.x, pos.y, pos.z);
return new Vector3d(fabricPlayer.getX(), fabricPlayer.getY(), fabricPlayer.getZ());
}
@Override
@@ -125,5 +129,28 @@ public class FabricPlayer implements PlatformPlayer {
fabricPlayer.hurtMarked = true;
}
@Override
public Vector3d getVelocity() {
final Vec3 fabricVelocity = fabricPlayer.getDeltaMovement();
return new Vector3d(fabricVelocity.x, fabricVelocity.y, fabricVelocity.z);
}
@Override
public double getJumpPower() {
double jumpVelocity = 0.42;
MobEffectInstance jumpEffect = fabricPlayer.getEffect(MobEffects.JUMP);
if (jumpEffect != null) {
int amplifier = jumpEffect.getAmplifier();
jumpVelocity += (amplifier + 1) * 0.1F;
}
return jumpVelocity;
}
@Override
public BoundingBox getBoundingBox() {
AABB boundingBox = fabricPlayer.getBoundingBox();
return new BoundingBox(boundingBox.minX, boundingBox.minY, boundingBox.minZ, boundingBox.maxX, boundingBox.minY, boundingBox.maxZ);
}
// Implement other methods
}
@@ -1,6 +1,6 @@
package me.caseload.knockbacksync.scheduler;
import me.caseload.knockbacksync.KBSyncFabricLoaderMod;
import me.caseload.knockbacksync.FabricLoaderMod;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.minecraft.server.MinecraftServer;
@@ -32,7 +32,7 @@ public class FabricSchedulerAdapter implements SchedulerAdapter {
@Override
public AbstractTaskHandle runTask(Runnable task) {
ScheduledTask scheduledTask = new ScheduledTask(task, KBSyncFabricLoaderMod.getServer().getTickCount(), 0, false);
ScheduledTask scheduledTask = new ScheduledTask(task, FabricLoaderMod.getServer().getTickCount(), 0, false);
Runnable cancellationTask = () -> taskMap.remove(scheduledTask);
taskMap.put(scheduledTask, cancellationTask);
return new FabricTaskHandle(cancellationTask);
@@ -52,7 +52,7 @@ public class FabricSchedulerAdapter implements SchedulerAdapter {
@Override
public AbstractTaskHandle runTaskLater(Runnable task, long delayTicks) {
ScheduledTask scheduledTask = new ScheduledTask(task, KBSyncFabricLoaderMod.getServer().getTickCount() + delayTicks, 0, false);
ScheduledTask scheduledTask = new ScheduledTask(task, FabricLoaderMod.getServer().getTickCount() + delayTicks, 0, false);
Runnable cancellationTask = () -> taskMap.remove(scheduledTask);
taskMap.put(scheduledTask, cancellationTask);
return new FabricTaskHandle(cancellationTask);
@@ -60,7 +60,7 @@ public class FabricSchedulerAdapter implements SchedulerAdapter {
@Override
public AbstractTaskHandle runTaskTimer(Runnable task, long delayTicks, long periodTicks) {
ScheduledTask scheduledTask = new ScheduledTask(task, KBSyncFabricLoaderMod.getServer().getTickCount() + delayTicks, periodTicks, true);
ScheduledTask scheduledTask = new ScheduledTask(task, FabricLoaderMod.getServer().getTickCount() + delayTicks, periodTicks, true);
Runnable cancellationTask = () -> taskMap.remove(scheduledTask);
taskMap.put(scheduledTask, cancellationTask);
return new FabricTaskHandle(cancellationTask);
@@ -1,7 +1,7 @@
package me.caseload.knockbacksync.sender;
import me.caseload.knockbacksync.KBSyncFabricBase;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.FabricBase;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.mixin.CommandStackSourceAccessor;
import me.caseload.knockbacksync.permission.FabricPermissionChecker;
import net.minecraft.commands.CommandSource;
@@ -13,16 +13,16 @@ import org.incendo.cloud.SenderMapper;
import java.util.UUID;
public class FabricSenderFactory extends SenderFactory<KBSyncFabricBase, CommandSourceStack> implements SenderMapper<CommandSourceStack, Sender> {
private final KBSyncFabricBase plugin;
public class FabricSenderFactory extends SenderFactory<FabricBase, CommandSourceStack> implements SenderMapper<CommandSourceStack, Sender> {
private final FabricBase plugin;
public FabricSenderFactory(KBSyncFabricBase kbSyncFabricBase) {
public FabricSenderFactory(FabricBase kbSyncFabricBase) {
super(kbSyncFabricBase);
this.plugin = kbSyncFabricBase;
}
@Override
protected KBSyncFabricBase getPlugin() {
protected FabricBase getPlugin() {
return this.plugin;
}
@@ -58,12 +58,12 @@ public class FabricSenderFactory extends SenderFactory<KBSyncFabricBase, Command
@Override
protected boolean hasPermission(CommandSourceStack commandSource, String node) {
return ((FabricPermissionChecker) KnockbackSyncBase.INSTANCE.getPermissionChecker()).hasPermission(commandSource, node);
return ((FabricPermissionChecker) Base.INSTANCE.getPermissionChecker()).hasPermission(commandSource, node);
}
@Override
protected boolean hasPermission(CommandSourceStack commandSource, String node, boolean defaultIfUnset) {
return ((FabricPermissionChecker) KnockbackSyncBase.INSTANCE.getPermissionChecker()).hasPermission(commandSource, node, defaultIfUnset);
return ((FabricPermissionChecker) Base.INSTANCE.getPermissionChecker()).hasPermission(commandSource, node, defaultIfUnset);
}
@Override
@@ -0,0 +1,97 @@
package me.caseload.knockbacksync.stats.custom;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import net.fabricmc.loader.api.FabricLoader;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
public class BStatsConfig {
private static final String HEADER = """
# bStats (https://bStats.org) collects some basic information for plugin authors, like how
# many people use their plugin and their total player count. It's recommended to keep bStats
# enabled, but if you're not comfortable with this, you can turn this setting off. There is no
# performance penalty associated with having metrics enabled, and data sent to bStats is fully
# anonymous.
""";
public static Config loadConfig() {
File bStatsFolder = new File(FabricLoader.getInstance().getConfigDir().toString(), "bStats");
File configFile = new File(bStatsFolder, "config.yml");
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
options.setPrettyFlow(true);
Yaml yaml = new Yaml(options);
Map<String, Object> data;
Config config = new Config();
try {
if (!configFile.exists()) {
bStatsFolder.mkdirs();
// Create default config
data = new LinkedHashMap<>();
data.put("enabled", true);
data.put("serverUuid", UUID.randomUUID().toString());
data.put("logFailedRequests", false);
data.put("logSentData", false);
data.put("logResponseStatusText", false);
// Write config with header
try (Writer writer = new OutputStreamWriter(new FileOutputStream(configFile), StandardCharsets.UTF_8)) {
writer.write(HEADER);
yaml.dump(data, writer);
}
} else {
// Load existing config
data = yaml.load(new FileInputStream(configFile));
if (data == null) {
data = new LinkedHashMap<>();
}
}
// Map the data to Config object
config.enabled = getBoolean(data, "enabled", true);
config.serverUuid = getString(data, "serverUuid", UUID.randomUUID().toString());
config.logFailedRequests = getBoolean(data, "logFailedRequests", false);
config.logSentData = getBoolean(data, "logSentData", false);
config.logResponseStatusText = getBoolean(data, "logResponseStatusText", false);
} catch (IOException e) {
e.printStackTrace();
// Fallback to default values
config.enabled = true;
config.serverUuid = UUID.randomUUID().toString();
config.logFailedRequests = false;
config.logSentData = false;
config.logResponseStatusText = false;
}
return config;
}
private static boolean getBoolean(Map<String, Object> map, String key, boolean defaultValue) {
Object value = map.get(key);
return value instanceof Boolean ? (Boolean) value : defaultValue;
}
private static String getString(Map<String, Object> map, String key, String defaultValue) {
Object value = map.get(key);
return value instanceof String ? (String) value : defaultValue;
}
// Your existing Config class
public static class Config {
public boolean enabled = true;
public String serverUuid;
public boolean logFailedRequests = false;
public boolean logSentData = false;
public boolean logResponseStatusText = false;
}
}
@@ -1,11 +1,11 @@
package me.caseload.knockbacksync.stats.custom;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.Base;
public class FabricStatsManager extends StatsManager {
@Override
public void init() {
KnockbackSyncBase.INSTANCE.getScheduler().runTaskAsynchronously(() -> {
Base.INSTANCE.getScheduler().runTaskAsynchronously(() -> {
BuildTypePie.determineBuildType(); // Function to calculate hash
MetricsFabric metrics = new MetricsFabric(23568);
metrics.addCustomChart(new PlayerVersionsPie());
@@ -15,17 +15,12 @@ package me.caseload.knockbacksync.stats.custom;
* Violations will result in a ban of your plugin and account from bStats.
*/
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import me.caseload.knockbacksync.KBSyncFabricLoaderMod;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.FabricLoaderMod;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.stats.CustomChart;
import me.caseload.knockbacksync.stats.JsonObjectBuilder;
import net.fabricmc.loader.api.FabricLoader;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
import java.util.logging.Level;
public class MetricsFabric implements Metrics {
@@ -40,34 +35,7 @@ public class MetricsFabric implements Metrics {
*/
public MetricsFabric(int serviceId) {
// Get the config file
File bStatsFolder = new File(FabricLoader.getInstance().getConfigDir().toString(), "bStats");
File configFile = new File(bStatsFolder, "config.yml");
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
Config config;
try {
if (!configFile.exists()) {
bStatsFolder.mkdirs(); // Ensure the directory exists
config = new Config();
config.serverUuid = UUID.randomUUID().toString();
// Add header as a comment
// mapper.writeValue(configFile,
// "# bStats (https://bStats.org) collects some basic information for plugin authors, like how\n"
// + "# many people use their plugin and their total player count. It's recommended to keep bStats\n"
// + "# enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n"
// + "# performance penalty associated with having metrics enabled, and data sent to bStats is fully\n"
// + "# anonymous.\n");
mapper.writeValue(configFile, config); // Write the config object as YAML
} else {
config = mapper.readValue(configFile, Config.class);
}
} catch (IOException ignored) {
// Handle the exception appropriately (e.g., log an error)
config = new Config(); // Fallback to default values
config.serverUuid = UUID.randomUUID().toString();
}
BStatsConfig.Config config = BStatsConfig.loadConfig();
// Load the data
boolean enabled = config.enabled;
@@ -91,10 +59,10 @@ public class MetricsFabric implements Metrics {
enabled,
this::appendPlatformData,
this::appendServiceData,
submitDataTask -> KnockbackSyncBase.INSTANCE.getScheduler().runTask(submitDataTask),
submitDataTask -> Base.INSTANCE.getScheduler().runTask(submitDataTask),
() -> true,
(message, error) -> KnockbackSyncBase.INSTANCE.getLogger().log(Level.WARNING, message, error),
(message) -> KnockbackSyncBase.INSTANCE.getLogger().log(Level.INFO, message),
(message, error) -> Base.INSTANCE.getLogger().log(Level.WARNING, message, error),
(message) -> Base.INSTANCE.getLogger().log(Level.INFO, message),
logErrors,
logSentData,
logResponseStatusText,
@@ -119,8 +87,8 @@ public class MetricsFabric implements Metrics {
private void appendPlatformData(JsonObjectBuilder builder) {
builder.appendField("playerAmount", getPlayerAmount());
builder.appendField("onlineMode", KBSyncFabricLoaderMod.getServer().usesAuthentication() ? 0 : 1);
builder.appendField("bukkitVersion", "Fabric " + FabricLoader.getInstance().getModContainer("fabricloader").get().getMetadata().getVersion().getFriendlyString() + " (MC: " + KBSyncFabricLoaderMod.getServer().getServerVersion() + ")");
builder.appendField("onlineMode", FabricLoaderMod.getServer().usesAuthentication() ? 0 : 1);
builder.appendField("bukkitVersion", "Fabric " + FabricLoader.getInstance().getModContainer("fabricloader").get().getMetadata().getVersion().getFriendlyString() + " (MC: " + FabricLoaderMod.getServer().getServerVersion() + ")");
builder.appendField("bukkitName", "Fabric");
builder.appendField("javaVersion", System.getProperty("java.version"));
builder.appendField("osName", System.getProperty("os.name"));
@@ -134,18 +102,10 @@ public class MetricsFabric implements Metrics {
}
private int getPlayerAmount() {
if (KBSyncFabricLoaderMod.getServer().isRunning()) {
return KBSyncFabricLoaderMod.getServer().getPlayerCount();
if (FabricLoaderMod.getServer().isRunning()) {
return FabricLoaderMod.getServer().getPlayerCount();
} else {
return 0;
}
}
private static class Config {
public boolean enabled = true;
public String serverUuid;
public boolean logFailedRequests = false;
public boolean logSentData = false;
public boolean logResponseStatusText = false;
}
}
@@ -0,0 +1,58 @@
package me.caseload.knockbacksync.util;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.ConfigWrapper;
import me.caseload.knockbacksync.mixin.UpdateIntervalAccessor;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerEntity;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import java.util.HashMap;
import java.util.Map;
public class CustomUpdateIntervals {
private static final Map<String, Integer> entityIntervals = new HashMap<>();
public static void setInterval(String entityType, int interval) {
entityIntervals.put(entityType, interval);
}
public static int getInterval(String entityType, int defaultInterval) {
return entityIntervals.getOrDefault(entityType, defaultInterval);
}
public static void clear() {
entityIntervals.clear();
}
public static void updateIntervals(ServerLevel level, Map<String, Integer> entityIntervals) {
for (Entity entity : level.getAllEntities()) {
String entityType = entity.getType().builtInRegistryHolder().getRegisteredName();
if (entityIntervals.containsKey(entityType)) {
ChunkMap.TrackedEntity serverEntity = ((ServerChunkCache) entity.level().getChunkSource()).chunkMap.entityMap.get(entity.getId());
if (serverEntity != null) {
((UpdateIntervalAccessor) serverEntity).setUpdateInterval(entityIntervals.get(entityType));
}
}
}
}
public static Map<String, Integer> loadEntityIntervalsFromConfig() {
Map<String, Integer> intervals = new HashMap<>();
ConfigWrapper config = Base.INSTANCE.getConfigManager().getConfigWrapper();
String basePath = "entity_update_intervals";
if (config.contains(basePath)) {
for (String entityType : config.getKeys(basePath)) {
int interval = config.getInt(basePath + "." + entityType, -1);
if (interval > 0) {
intervals.put(entityType, interval);
}
}
}
return intervals;
}
}
@@ -1,6 +1,6 @@
package me.caseload.knockbacksync.world;
import me.caseload.knockbacksync.KBSyncFabricLoaderMod;
import me.caseload.knockbacksync.FabricLoaderMod;
import me.caseload.knockbacksync.player.FabricPlayer;
import me.caseload.knockbacksync.player.PlatformPlayer;
@@ -10,14 +10,14 @@ import java.util.stream.Collectors;
public class FabricServer implements PlatformServer {
public Collection<PlatformPlayer> getOnlinePlayers() {
return KBSyncFabricLoaderMod.getServer().getPlayerList().getPlayers().stream()
return FabricLoaderMod.getServer().getPlayerList().getPlayers().stream()
.map(FabricPlayer::new)
.collect(Collectors.toList());
}
@Override
public PlatformPlayer getPlayer(UUID uuid) {
return new FabricPlayer(KBSyncFabricLoaderMod.getServer().getPlayerList().getPlayer(uuid));
return new FabricPlayer(FabricLoaderMod.getServer().getPlayerList().getPlayer(uuid));
}
}
@@ -10,10 +10,12 @@ import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
public class FabricWorld implements PlatformWorld {
private final Level world;
@@ -25,7 +27,7 @@ public class FabricWorld implements PlatformWorld {
@Override
public WrappedBlockState getBlockStateAt(int x, int y, int z) {
BlockState blockState = this.world.getBlockState(new BlockPos(x, y, z));
return WrappedBlockState.getByString(blockState.getBlock().getName().toString());
return WrappedBlockState.getByGlobalId(Block.BLOCK_STATE_REGISTRY.getId(blockState));
}
@Override
@@ -45,17 +47,18 @@ public class FabricWorld implements PlatformWorld {
fluidHandling == FluidHandling.NONE ? ClipContext.Fluid.NONE :
fluidHandling == FluidHandling.SOURCE_ONLY ? ClipContext.Fluid.SOURCE_ONLY :
ClipContext.Fluid.ANY,
(Entity) null
CollisionContext.empty()
));
if (result.getType() == HitResult.Type.MISS) return null;
Vec3 hitLocation = result.getLocation();
BlockPos blockPos = result.getBlockPos();
return new RayTraceResult(
new Vector3d(result.getLocation().x, result.getLocation().y, result.getLocation().z),
new Vector3d(hitLocation.x, hitLocation.y, hitLocation.z),
BlockFace.getBlockFaceByValue(result.getDirection().ordinal()),
new Vector3i(result.getBlockPos().getX(), result.getBlockPos().getY(), result.getBlockPos().getZ()),
WrappedBlockState.getByString(world.getBlockState(blockPos).getBlock().toString())
new Vector3i(blockPos.getX(), blockPos.getY(), blockPos.getZ()),
getBlockStateAt(blockPos.getX(), blockPos.getY(), blockPos.getZ())
);
}
}
+3 -2
View File
@@ -11,7 +11,7 @@
"environment": "*",
"entrypoints": {
"main": [
"me.caseload.knockbacksync.KBSyncFabricLoaderMod"
"me.caseload.knockbacksync.FabricLoaderMod"
]
},
"mixins": [
@@ -21,5 +21,6 @@
"fabricloader": ">=${loader_version}",
"fabric": "*",
"minecraft": "~${minecraft_version}"
}
},
"accessWidener": "knockbacksync.accesswidener"
}
@@ -0,0 +1,6 @@
accessWidener v2 named
# We don't actually need this right now, but it's useful to keep an access widener around for when we do need one
accessible field net/minecraft/server/level/ChunkMap entityMap Lit/unimi/dsi/fastutil/ints/Int2ObjectMap;
accessible class net/minecraft/server/level/ChunkMap$TrackedEntity
accessible method net/minecraft/server/level/ChunkMap$TrackedEntity updatePlayers (Ljava/util/List;)V
@@ -4,6 +4,7 @@
"package": "me.caseload.knockbacksync.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [
"ChunkMapMixin",
"CommandStackSourceAccessor",
"PlayerMixin",
"ServerEntityMixin",
+2
View File
@@ -17,3 +17,5 @@ archives_base_name=knockbacksync
# Dependencies
# check this on https://modmuss50.me/fabric.html
fabric_version=0.102.0+1.21
githubRepo=CASELOAD7000/knockback-sync
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-136
View File
@@ -1,136 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.caseload</groupId>
<artifactId>KnockbackSync</artifactId>
<version>1.3.2</version>
<packaging>jar</packaging>
<name>KnockbackSync</name>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!--outputTimestamp must be set to a consistent arbitrary value for reproducibility-->
<!--Chosen value is the exact time caseload uploaded the first commit-->
<project.build.outputTimestamp>2024-01-18T02:53:12Z</project.build.outputTimestamp>
</properties>
<build>
<defaultGoal>clean package</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<minimizeJar>true</minimizeJar>
<relocations>
<relocation>
<pattern>dev.jorel.commandapi</pattern>
<shadedPattern>me.caseload.knockbacksync.commandapi</shadedPattern>
</relocation>
<relocation>
<pattern>com.github.retrooper</pattern>
<shadedPattern>me.caseload.knockbacksync.retrooper</shadedPattern>
</relocation>
<relocation>
<pattern>org.projectlombok</pattern>
<shadedPattern>me.caseload.knockbacksync.projectlombok</shadedPattern>
</relocation>
<relocation>
<pattern>org.kohsuke</pattern>
<shadedPattern>me.caseload.knockbacksync.kohsuke</shadedPattern>
</relocation>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>me.caseload.knockbacksync.bstats</shadedPattern>
</relocation>
</relocations>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
<repository>
<id>spigotmc-repo</id> <!--spigot-api-->
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository> <!--packetevents-spigot-->
<id>codemc-releases</id>
<url>https://repo.codemc.io/repository/maven-releases/</url>
</repository>
<repository> <!--Backup repo for maven central, mainly for geysermc api, bstats-bukkit-->
<id>opencollab</id>
<url>https://repo.opencollab.dev/maven-snapshots/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.18.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.retrooper</groupId>
<artifactId>packetevents-spigot</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>dev.jorel</groupId>
<artifactId>commandapi-bukkit-shade</artifactId>
<version>9.5.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.kohsuke</groupId>
<artifactId>github-api</artifactId>
<version>1.326</version>
</dependency>
<dependency>
<groupId>org.geysermc.floodgate</groupId>
<artifactId>api</artifactId>
<version>2.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>3.0.2</version>
</dependency>
</dependencies>
</project>