- Fix NPE with combat task pings. All combat task management is now done in player netty threads.

Bukkit:
- Disable paper plugin remapping
- Build a "lite" version that doesn't shade PacketEvents, allows multiple plugins to share PacketEvents or for you to update PacketEvents without rebuilding KnockbackSync

Fabric:
- Actually call onLoad() to init PE
- Explicitly prevent loading on Client
This commit is contained in:
Axionize committed 2025-03-14 23:19:13 -04:00
1 parent bf609a1964
commit a59ff6e625
12 files changed
+147 -74

No files matched your search

+34 -44
View File
@@ -29,7 +29,7 @@ jobs:
- name: Setup Gradle 8.10
uses: gradle/actions/setup-gradle@v4
with:
gradle-version: "8.10" # Quotes required to prevent YAML converting to number
gradle-version: "8.13" # Quotes required to prevent YAML converting to number
- name: Generate build number
id: buildnumber
@@ -53,39 +53,26 @@ jobs:
# Ensure snapshot is set to false
sed -i "s/val snapshot = .*/val snapshot = false/" build.gradle.kts
- name: Build with Gradle
run: gradle clean build -x test
- name: Build without PE shaded
run: gradle build -PshadePE=false -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
- name: Build with PE shaded Gradle
run: gradle build -PshadePE=true -x test
env:
GITHUB_REPOSITORY: ${{ github.repository }}
# Download PaperSpigot (Choose version for reproducibility
- name: Download PaperSpigot
run: |
mkdir -p paper-server/plugins
curl -o paperclip.jar https://api.papermc.io/v2/projects/paper/versions/1.21.1/builds/120/downloads/paper-1.21.1-120.jar
mv paperclip.jar paper-server/
# Copy Plugin into Plugins Folder and Start Server
- name: Copy Plugin and Start Paper Server
run: |
cp bukkit/build/libs/knockbacksync-bukkit-${{ env.NEW_VERSION }}.jar paper-server/plugins/
cd paper-server
# Start the server to remap the plugin
timeout 60 java -Xms3G -Xmx3G -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:+AlwaysPreTouch -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1 -jar -Dcom.mojang.eula.agree=true paperclip.jar --nogui || true
# Get the Hashes of the Original and Remapped JARs
# Get the Hashes of the Original and Lite JARs
- name: Calculate Hashes
run: |
# Calculate hash for the original JAR
ORIGINAL_HASH=$(sha256sum bukkit/build/libs/knockbacksync-bukkit-${{ env.NEW_VERSION }}.jar | awk '{print $1}')
echo "ORIGINAL_HASH=$ORIGINAL_HASH" >> $GITHUB_ENV
# Calculate hash for the remapped JAR
REMAPPED_HASH=$(sha256sum paper-server/plugins/.paper-remapped/knockbacksync-bukkit-${{ env.NEW_VERSION }}.jar | awk '{print $1}')
echo "REMAPPED_HASH=$REMAPPED_HASH" >> $GITHUB_ENV
# Calculate hash for the remapped JAR
BUKKIT_HASH=$(sha256sum bukkit/build/libs/knockbacksync-bukkit-${{ env.NEW_VERSION }}.jar | awk '{print $1}')
echo "BUKKIT_HASH=$BUKKIT_HASH" >> $GITHUB_ENV
# Calculate hash for the lite JAR
BUKKIT_LITE_HASH=$(sha256sum bukkit/build/libs/knockbacksync-bukkit-lite-${{ env.NEW_VERSION }}.jar | awk '{print $1}')
echo "BUKKIT_LITE_HASH=$BUKKIT_LITE_HASH" >> $GITHUB_ENV
# Calculate hash for the lite JAR
FABRIC_HASH=$(sha256sum fabric/build/libs/knockbacksync-fabric-${{ env.NEW_VERSION }}.jar | awk '{print $1}')
echo "FABRIC_HASH=$FABRIC_HASH" >> $GITHUB_ENV
@@ -111,8 +98,8 @@ jobs:
}
# Add new hashes if they don't exist
add_hash_if_new "${{ env.ORIGINAL_HASH }}"
add_hash_if_new "${{ env.REMAPPED_HASH }}"
add_hash_if_new "${{ env.BUKKIT_HASH }}"
add_hash_if_new "${{ env.BUKKIT_LITE_HASH }}"
add_hash_if_new "${{ env.FABRIC_HASH }}"
# Check if the file has changed
@@ -143,23 +130,25 @@ jobs:
# Clean up
rm dev-builds.txt.original
- name: Create artifacts directory
run: mkdir -p artifacts
- name: Copy bukkit artifact
run: cp bukkit/build/libs/knockbacksync-bukkit-${{ env.NEW_VERSION }}.jar artifacts/
- name: Copy fabric artifact
run: cp fabric/build/libs/knockbacksync-fabric-${{ env.NEW_VERSION }}.jar artifacts/
- name: Zip the artifacts
run: zip -r KnockbackSync-${{ env.NEW_VERSION }}.zip artifacts/*
- name: Upload artifacts zip
- name: Upload Bukkit Artifact
uses: actions/upload-artifact@v4
with:
name: KnockbackSync-${{ env.NEW_VERSION }}.zip
path: KnockbackSync-${{ env.NEW_VERSION }}.zip
name: knockbacksync-bukkit-${{ env.NEW_VERSION }}.jar
path: bukkit/build/libs/knockbacksync-bukkit-${{ env.NEW_VERSION }}.jar
retention-days: 90
- name: Upload Bukkit Lite Artifact
uses: actions/upload-artifact@v4
with:
name: knockbacksync-bukkit-lite-${{ env.NEW_VERSION }}.jar
path: bukkit/build/libs/knockbacksync-bukkit-lite-${{ env.NEW_VERSION }}.jar
retention-days: 90
- name: Upload Fabric Artifact
uses: actions/upload-artifact@v4
with:
name: knockbacksync-fabric-${{ env.NEW_VERSION }}.jar
path: fabric/build/libs/knockbacksync-fabric-${{ env.NEW_VERSION }}.jar
retention-days: 90
- uses: Kir-Antipov/mc-publish@v3.3
@@ -171,6 +160,7 @@ jobs:
files: |
bukkit/build/libs/knockbacksync-bukkit-${{ env.NEW_VERSION }}.jar
bukkit/build/libs/knockbacksync-bukkit-lite-${{ env.NEW_VERSION }}.jar
name: KnockbackSync-${{ env.NEW_VERSION }}
version: ${{ env.NEW_VERSION }}
+5 -1
View File
@@ -3,13 +3,17 @@ import java.io.ByteArrayOutputStream
plugins {
id("java")
id("com.gradleup.shadow") version "8.3.3" apply false
id("fabric-loom") version "1.8-SNAPSHOT" apply false
id("fabric-loom") version "1.10.2" apply false
}
val fullVersion = "1.3.5"
val snapshot = true
val githubRepo = System.getenv("GITHUB_REPOSITORY") ?: project.findProperty("githubRepo").toString()
extra["shadePE"] = project.findProperty("shadePE")?.toString()?.toBoolean()
?: System.getenv("SHADE_PE")?.toBoolean()
?: true
allprojects {
fun getVersionMeta(includeHash: Boolean): String {
if (!snapshot) {
+19 -3
View File
@@ -4,8 +4,10 @@ plugins {
id("com.gradleup.shadow")
}
val shadePE: Boolean by rootProject.extra
base {
archivesName.set("${rootProject.property("archives_base_name")}-bukkit")
archivesName.set("${rootProject.property("archives_base_name")}-bukkit${if (shadePE) "" else "-lite"}")
}
val shadeThisThing: Configuration by configurations.creating {
@@ -31,13 +33,17 @@ dependencies {
compileOnly("org.spigotmc:spigot-api:1.18.2-R0.1-SNAPSHOT")
compileOnly("org.geysermc.floodgate:api:2.0-SNAPSHOT")
compileOnly("io.netty:netty-all:4.1.72.Final")
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.github.retrooper:packetevents-spigot:2.7.0-SNAPSHOT")!!)
// shadeThisThing(implementation("net.kyori:adventure-platform-bukkit:4.3.4")!!)
if (shadePE) {
shadeThisThing(implementation("com.github.retrooper:packetevents-spigot:2.7.0-SNAPSHOT")!!)
} else {
compileOnly("com.github.retrooper:packetevents-spigot:2.7.0-SNAPSHOT")
}
shadeThisThing(implementation("org.incendo:cloud-paper:2.0.0-beta.10")!!)
shadeThisThing(implementation("org.incendo:cloud-core:2.0.0")!!)
@@ -46,6 +52,13 @@ dependencies {
}
tasks.withType<ShadowJar> {
manifest {
attributes["paperweight-mappings-namespace"] = "mojang"
}
// Remove the -all suffix from the output JAR file name
archiveClassifier.set("")
configurations = listOf(shadeThisThing)
isEnableRelocation = false
relocationPrefix = "${project.property("maven_group")}.${project.property("archives_base_name")}.shaded"
@@ -58,11 +71,14 @@ tasks.build {
tasks.processResources {
from(project(":common").sourceSets.main.get().resources)
inputs.property("version", project.version)
inputs.property("shadePE", shadePE) // Add shadePE as an input to trigger reprocessing if it changes
filteringCharset = "UTF-8"
filesMatching("plugin.yml") {
expand(
"version" to project.version,
"depends" to if (shadePE) "[]" else listOf("packetevents")
)
}
}
+1
View File
@@ -5,6 +5,7 @@ api-version: '1.13' # We actually support 1.12.2, api-version was added later th
authors: [ Caseload, Axionize ]
description: Synchronizes player knockback for smoother gameplay.
folia-supported: true
depends: ${depends}
permissions:
knockbacksync.update:
default: op
+9 -3
View File
@@ -2,17 +2,23 @@ plugins {
id("com.github.gmazzo.buildconfig") version "3.1.0"
}
val shadePE: Boolean by rootProject.extra
dependencies {
// True compileOnly deps
compileOnly("org.geysermc.floodgate:api:2.0-SNAPSHOT")
compileOnly("io.netty:netty-all:4.1.72.Final")
compileOnly("org.projectlombok:lombok:1.18.34")
annotationProcessor("org.projectlombok:lombok:1.18.34")
// Shaded in or bundled by platform-specific code
implementation("com.github.retrooper:packetevents-api:2.7.0-SNAPSHOT")
if (shadePE) {
implementation("com.github.retrooper:packetevents-api:2.7.0-SNAPSHOT")
} else {
compileOnly("com.github.retrooper:packetevents-api:2.7.0-SNAPSHOT")
}
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")
@@ -25,9 +25,7 @@ public abstract class PlayerJoinQuitListener {
if (playerData == null)
return;
if (playerData.isInCombat())
playerData.quitCombat();
playerData.quitCombat(true);
PlayerDataManager.removePlayerData(uuid);
}
}
@@ -8,17 +8,17 @@ import java.util.concurrent.ConcurrentHashMap;
public class CombatManager {
private static final Set<UUID> combatPlayers = ConcurrentHashMap.newKeySet();
private static final Set<@NotNull UUID> combatPlayers = ConcurrentHashMap.newKeySet();
public static @NotNull Set<UUID> getPlayers() {
public static @NotNull Set<@NotNull UUID> getPlayers() {
return combatPlayers;
}
public static void addPlayer(UUID uuid) {
public static void addPlayer(@NotNull UUID uuid) {
combatPlayers.add(uuid);
}
public static void removePlayer(UUID uuid) {
public static void removePlayer(@NotNull UUID uuid) {
combatPlayers.remove(uuid);
}
}
@@ -23,6 +23,7 @@ import me.caseload.knockbacksync.event.events.ToggleOnOffEvent;
import me.caseload.knockbacksync.manager.CombatManager;
import me.caseload.knockbacksync.manager.ConfigManager;
import me.caseload.knockbacksync.scheduler.AbstractTaskHandle;
import me.caseload.knockbacksync.scheduler.NettyTaskHandle;
import me.caseload.knockbacksync.util.MathUtil;
import me.caseload.knockbacksync.util.data.Pair;
import me.caseload.knockbacksync.world.PlatformWorld;
@@ -32,8 +33,10 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import io.netty.channel.Channel;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
@Getter
public class PlayerData {
@@ -77,6 +80,7 @@ public class PlayerData {
@Getter private final JitterCalculator jitterCalculator = new JitterCalculator();
@Setter private double jitter;
@Nullable private AbstractTaskHandle combatTask;
@NotNull private final Object combatTaskLock = new Object(); // Lock object for synchronization
@Nullable @Setter private Double ping, previousPing;
@Nullable @Setter private Double verticalVelocity;
@Nullable @Setter private Integer lastDamageTicks;
@@ -292,28 +296,41 @@ public class PlayerData {
return yAxis;
}
public boolean isInCombat() {
return combatTask != null;
}
public void updateCombat() {
if (isInCombat())
combatTask.cancel();
combatTask = newCombatTask();
CombatManager.addPlayer(uuid);
Channel channel = (Channel) user.getChannel();
channel.eventLoop().execute(() -> {
if (combatTask != null) {
combatTask.cancel();
}
combatTask = newCombatTask(channel);
CombatManager.addPlayer(uuid);
});
}
public void quitCombat() {
if (combatTask != null) combatTask.cancel(); // rarely throws NPE without null check
combatTask = null;
CombatManager.removePlayer(uuid);
/**
* Cancels the combatTask quit task. This prevents quitCombat() from being called later against a new combat task
* Or against a player that has disconnected already
* @param async Whether we are already in the players netty thread
*/
public void quitCombat(boolean async) {
Channel channel = (Channel) user.getChannel();
Runnable runnable = () -> {
if (combatTask != null) {
combatTask.cancel();
combatTask = null;
}
CombatManager.removePlayer(uuid);
};
if (async) {
channel.eventLoop().execute(runnable);
} else {
runnable.run();
}
}
@NotNull
private AbstractTaskHandle newCombatTask() {
return Base.INSTANCE.getScheduler().runTaskLaterAsynchronously(
this::quitCombat, Base.INSTANCE.getConfigManager().getCombatTimer());
private AbstractTaskHandle newCombatTask(Channel channel) {
return new NettyTaskHandle(channel.eventLoop().schedule(() -> quitCombat(false), convertTicksToMilliseconds(Base.INSTANCE.getConfigManager().getCombatTimer()), TimeUnit.MILLISECONDS));
}
public ClientVersion getClientVersion() {
@@ -360,4 +377,8 @@ public class PlayerData {
throw new IllegalStateException("Unknown ping_strategy: " + pingStrategy);
}
}
private long convertTicksToMilliseconds(long ticks) {
return ticks * 50;
}
}
@@ -0,0 +1,22 @@
package me.caseload.knockbacksync.scheduler;
import io.netty.util.concurrent.ScheduledFuture;
public class NettyTaskHandle implements AbstractTaskHandle {
private final ScheduledFuture scheduledFuture;
public NettyTaskHandle(ScheduledFuture<?> scheduledFuture) {
this.scheduledFuture = scheduledFuture;
}
@Override
public boolean getCancelled() {
return scheduledFuture.isCancelled();
}
@Override
public void cancel() {
scheduledFuture.cancel(true);
}
}
+3
View File
@@ -3,6 +3,8 @@ plugins {
id("com.gradleup.shadow")
}
val shadePE: Boolean by rootProject.extra
loom {
accessWidenerPath = file("src/main/resources/knockbacksync.accesswidener")
}
@@ -57,6 +59,7 @@ dependencies {
compileOnly("org.geysermc.floodgate:api:2.0-SNAPSHOT")
compileOnly("org.projectlombok:lombok:1.18.34")
compileOnly("io.netty:netty-all:4.1.72.Final")
annotationProcessor("org.projectlombok:lombok:1.18.34")
}
@@ -1,5 +1,6 @@
package me.caseload.knockbacksync;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.loader.api.FabricLoader;
@@ -16,15 +17,23 @@ public class FabricLoaderMod implements PreLaunchEntrypoint, ModInitializer {
@Override
public void onPreLaunch() {
ensureServer();
core.load();
}
@Override
public void onInitialize() {
ensureServer();
core.enable();
ServerLifecycleEvents.SERVER_STOPPING.register((server) -> {
core.scheduler.shutdown();
core.statsManager.getMetrics().shutdown();
});
}
private void ensureServer() {
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
throw new IllegalStateException("This mod can only be run on servers");
}
}
}
@@ -10,6 +10,9 @@
"icon": "assets/knockbacksync/icon.png",
"environment": "*",
"entrypoints": {
"preLaunch": [
"me.caseload.knockbacksync.FabricLoaderMod"
],
"main": [
"me.caseload.knockbacksync.FabricLoaderMod"
]