11 Commits
37 changed files with 921 additions and 155 deletions

No files matched your search

+2 -2
View File
@@ -204,7 +204,7 @@ jobs:
purpur
game-versions: |
>=1.14.4
>=1.12.2
retry-attempts: 2
retry-delay: 10000
@@ -222,7 +222,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/
+1 -1
View File
@@ -29,7 +29,7 @@ tasks.named<JavaCompile>("compileTestJava") {
dependencies {
implementation(project(":common"))
compileOnly("org.spigotmc:spigot-api:1.14.4-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")
@@ -16,6 +16,7 @@ import me.caseload.knockbacksync.sender.Sender;
import me.caseload.knockbacksync.stats.custom.BukkitStatsManager;
import me.caseload.knockbacksync.stats.custom.PluginJarHashProvider;
import me.caseload.knockbacksync.world.BukkitServer;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
@@ -1,18 +1,20 @@
package me.caseload.knockbacksync.player;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.PacketEventsAPI;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
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.PlatformWorld;
import me.caseload.knockbacksync.world.SpigotWorld;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
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;
@@ -23,21 +25,44 @@ public class BukkitPlayer implements PlatformPlayer {
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 {
String bukkitversion = Bukkit.getServer().getClass().getPackage()
.getName();
// Step 1: Load the CraftPlayer class
craftPlayerClass = Class.forName(bukkitversion + ".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("net.minecraft.server." + bukkitversion + ".EntityPlayer");
getAttackStrengthScaleMethod = entityPlayerClass.getDeclaredMethod("getAttackStrengthScale", float.class);
getAttackStrengthScaleMethod.setAccessible(true);
// 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) {
e.printStackTrace();
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);
}
}
@@ -93,7 +118,11 @@ public class BukkitPlayer implements PlatformPlayer {
@Override
public int getPing() {
return PacketEvents.getAPI().getPlayerManager().getPing(bukkitPlayer);
if (currentVersion.isNewerThanOrEquals(ServerVersion.V_1_16_5)) {
return bukkitPlayer.getPing();
} else {
return PacketEvents.getAPI().getPlayerManager().getPing(bukkitPlayer);
}
}
@Override
@@ -107,9 +136,9 @@ public class BukkitPlayer implements PlatformPlayer {
}
@Override
public Vector3d getLocation() {
Location location = bukkitPlayer.getLocation();
return new Vector3d(location.getX(), location.getY(), location.getZ());
public Location getLocation() {
org.bukkit.Location location = bukkitPlayer.getLocation();
return new Location(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
}
@Override
@@ -119,16 +148,19 @@ public class BukkitPlayer implements PlatformPlayer {
@Override
public double getAttackCooldown() {
try {
// Step 1: Get the CraftPlayer instance
Object craftPlayer = craftPlayerClass.cast(bukkitPlayer);
// Step 2: Get the handle (NMS EntityPlayer)
Object entityPlayer = getHandleMethod.invoke(craftPlayer);
// Step 3: Invoke the getAttackStrengthScale method
return (float) getAttackStrengthScaleMethod.invoke(entityPlayer, 0.5f);
} catch (Exception e) {
e.printStackTrace();
return -1; // Return -1 if an error occurs
if (currentVersion.isNewerThan(ServerVersion.V_1_14_4)) {
return bukkitPlayer.getAttackCooldown();
} else {
try {
// Step 1: Get the CraftPlayer instance
// Object craftPlayer = craftPlayerClass.cast(bukkitPlayer);
// 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!");
}
}
}
@@ -151,4 +183,10 @@ public class BukkitPlayer implements PlatformPlayer {
public void setVelocity(Vector3d adjustedVelocity) {
bukkitPlayer.setVelocity(new Vector(adjustedVelocity.x, adjustedVelocity.y, adjustedVelocity.z));
}
@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,19 +1,58 @@
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 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;
}
@@ -50,22 +89,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 ? WrappedBlockState.getByString(result.getHitBlock().getType().name()) : 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);
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
name: KnockbackSync
version: ${version}
main: me.caseload.knockbacksync.KBSyncBukkitLoaderPlugin
api-version: '1.14'
authors: [ Caseload ]
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:
@@ -9,6 +9,9 @@ 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.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;
@@ -39,6 +42,7 @@ public abstract class KnockbackSyncBase {
@Getter protected SchedulerAdapter scheduler;
@Getter protected ConfigManager configManager;
protected CommandManager<Sender> commandManager;
private final EventBus simpleEventBus = new OptimizedEventBus();
@Getter
protected AbstractPlayerSelectorParser<Sender> playerSelectorParser;
@@ -102,6 +106,7 @@ public abstract class KnockbackSyncBase {
new AttributeChangeListener(),
new PingReceiveListener()
);
Event.setEventBus(simpleEventBus);
}
protected abstract void registerPlatformListeners();
@@ -1,8 +1,11 @@
package me.caseload.knockbacksync.command.subcommand;
import me.caseload.knockbacksync.ConfigWrapper;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.command.generic.BuilderCommand;
import me.caseload.knockbacksync.command.generic.PlayerSelector;
import me.caseload.knockbacksync.event.ConfigReloadEvent;
import me.caseload.knockbacksync.event.KBSyncEventHandler;
import me.caseload.knockbacksync.sender.Sender;
import me.caseload.knockbacksync.util.ChatUtil;
import org.incendo.cloud.CommandManager;
@@ -39,4 +42,6 @@ public class PingCommand implements BuilderCommand {
})
);
}
}
@@ -2,6 +2,8 @@ package me.caseload.knockbacksync.command.subcommand;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.command.generic.BuilderCommand;
import me.caseload.knockbacksync.event.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,37 @@ import org.incendo.cloud.permission.PredicatePermission;
import java.util.function.Predicate;
public class ReloadCommand implements BuilderCommand {
private final ConfigManager configManager = KnockbackSyncBase.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);
};
manager.commandBuilder("knockbacksync", "kbsync", "kbs")
.literal("reload")
.permission((sender -> {
final String permission = "knockbacksync.reload";
Predicate<Sender> senderPredicate = (s) -> {
return s.hasPermission(permission, false);
};
return PredicatePermission.of(senderPredicate).testPermission(sender);
}))
.handler(context -> {
ConfigManager configManager = KnockbackSyncBase.INSTANCE.getConfigManager();
configManager.loadConfig(true);
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);
})
);
}
}
@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.ConfigWrapper;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.command.generic.BuilderCommand;
import me.caseload.knockbacksync.command.generic.PlayerSelector;
import me.caseload.knockbacksync.event.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;
@@ -20,6 +23,14 @@ public class StatusCommand implements BuilderCommand {
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")
@@ -35,53 +46,60 @@ 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);
}
} else {
sender.sendMessage(ChatUtil.translateAlternateColorCodes('&', "&cYou do not have permisssion to check your knockbacksync status."));
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()));
}
} else {
sender.sendMessage(ChatUtil.translateAlternateColorCodes('&', "&cYou do not have permission to check your knockbacksync status."));
}
} else {
if (sender.hasPermission(STATUS_OTHER_PERMISSION)) {
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!"));
}
}
})
);
}
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)");
}
}
@@ -0,0 +1,14 @@
package me.caseload.knockbacksync.event;
import lombok.Getter;
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,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,7 @@
package me.caseload.knockbacksync.event;
public interface EventBus {
void registerListeners(Object listener);
void unregisterListeners(Object listener);
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,69 @@
package me.caseload.knockbacksync.event;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
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) {
for (Method method : listener.getClass().getDeclaredMethods()) {
KBSyncEventHandler annotation = method.getAnnotation(KBSyncEventHandler.class);
if (annotation != null && method.getParameterCount() == 1) {
Class<?> eventType = method.getParameterTypes()[0];
if (Event.class.isAssignableFrom(eventType)) {
try {
MethodHandle handle = lookup.unreflect(method);
OptimizedListener optimizedListener = new OptimizedListener(listener, handle, annotation.priority());
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.listener == listener));
}
@Override
public void post(Event event) {
List<OptimizedListener> listeners = listenerMap.get(event.getClass());
if (listeners != null) {
for (OptimizedListener listener : listeners) {
try {
listener.handle.invoke(listener.listener, event);
if (event.isCancelled()) {
break;
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
}
private static class OptimizedListener {
final Object listener;
final MethodHandle handle;
final int priority;
OptimizedListener(Object listener, MethodHandle handle, int priority) {
this.listener = listener;
this.handle = handle;
this.priority = priority;
}
}
}
@@ -35,7 +35,6 @@ public class ConfigManager {
private String playerEnableMessage;
private String playerDisableMessage;
private String playerIneligibleMessage;
private String reloadMessage;
private AbstractTaskHandle pingTask;
@@ -117,7 +116,6 @@ public class ConfigManager {
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.");
}
public void updateConfig() {
@@ -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.PlatformWorld;
import org.jetbrains.annotations.NotNull;
@@ -30,7 +32,7 @@ public interface PlatformPlayer {
PlatformWorld getWorld();
Vector3d getLocation();
Location getLocation();
void sendMessage(@NotNull String s);
@@ -43,5 +45,7 @@ public interface PlatformPlayer {
@Nullable Integer getNoDamageTicks();
void setVelocity(Vector3d adjustedVelocity);
BoundingBox getBoundingBox();
// Add more methods as needed
}
@@ -7,6 +7,8 @@ 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.Location;
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;
@@ -43,8 +45,8 @@ 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 {
@@ -177,7 +179,7 @@ public class PlayerData {
* @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());
WrappedBlockState blockState = platformPlayer.getWorld().getBlockStateAt(platformPlayer.getLocation().getPosition());
if (platformPlayer.isGliding() ||
blockState.getType() == StateTypes.WATER ||
@@ -215,13 +217,13 @@ public class PlayerData {
PlatformWorld world = platformPlayer.getWorld();
for (Vector3d corner : getBBCorners()) {
RayTraceResult result = world.rayTraceBlocks(corner, new Vector3d(0, -1, 0), 5, FluidHandling.NONE, true);
for (Location corner : getBBCorners()) {
RayTraceResult result = world.rayTraceBlocks(corner.getPosition(), 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;
@@ -232,17 +234,17 @@ 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;
private Location[] getBBCorners() {
BoundingBox boundingBox = platformPlayer.getBoundingBox();
Location 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 Location[] {
new Location(boundingBox.getMinX() + adjustment, location.getY(), boundingBox.getMinZ() + adjustment, 0, 0),
new Location(boundingBox.getMinX() + adjustment, location.getY(), boundingBox.getMaxZ() - adjustment, 0, 0),
new Location(boundingBox.getMaxX() - adjustment, location.getY(), boundingBox.getMinZ() + adjustment,0 , 0),
new Location(boundingBox.getMaxX() - adjustment, location.getY(), boundingBox.getMaxZ() - adjustment,0, 0)
};
}
@@ -49,7 +49,7 @@ public class ChatUtil {
if (playerData.getPing() == null) {
rawReturnString = String.format("Pong not received. %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 last ping packet took &b%.3f&rms. Jitter: &b%.3f&rms.", noun, playerData.getPing(), playerData.getJitter());
}
return ChatUtil.translateAlternateColorCodes('&', rawReturnString);
}
+140 -1
View File
@@ -3,7 +3,7 @@
#########################################
# Do not modify!
config-version: 2
config_version: 3
# Plugin enabled state
# Toggleable using /knockbacksync toggle
@@ -52,3 +52,142 @@ 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."
# 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
entity_tick_intervals:
player: 1 # This is 2 by default in vanilla and paper
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
+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")
}
@@ -0,0 +1,44 @@
package me.caseload.knockbacksync.entity;
import me.caseload.knockbacksync.ConfigWrapper;
import me.caseload.knockbacksync.KnockbackSyncBase;
import me.caseload.knockbacksync.event.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(KnockbackSyncBase.INSTANCE.getConfigManager().getConfigWrapper());
}
@KBSyncEventHandler
public static void updateTickIntervals(ConfigReloadEvent configReloadEvent) {
ConfigWrapper configWrapper = configReloadEvent.getConfigManager().getConfigWrapper();
updateTickIntervals(configWrapper);
}
private static void updateTickIntervals(ConfigWrapper configWrapper) {
customTickIntervals.clear();
for (String entityKey : configWrapper.getKeys("entity_tick_intervals")) {
try {
Optional<EntityType<?>> entityType = EntityType.byString(entityKey.toUpperCase());
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.getDescription().getString().toLowerCase(), 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,30 @@
package me.caseload.knockbacksync.mixin;
import com.google.common.collect.Maps;
import me.caseload.knockbacksync.ConfigWrapper;
import me.caseload.knockbacksync.entity.EntityTickManager;
import me.caseload.knockbacksync.event.ConfigReloadEvent;
import me.caseload.knockbacksync.event.KBSyncEventHandler;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
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.Redirect;
import java.util.Map;
import java.util.Optional;
@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) {
// BuiltInRegistries.ENTITY_TYPE.get(ResourceLocation.parse());
// Check if we have a custom interval for this entity type
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);
// }
// }
//}
@@ -1,7 +1,16 @@
package me.caseload.knockbacksync.mixin;
import me.caseload.knockbacksync.KBSyncFabricLoaderMod;
import me.caseload.knockbacksync.callback.PlayerVelocityEvent;
import me.caseload.knockbacksync.event.ConfigReloadEvent;
import me.caseload.knockbacksync.event.KBSyncEventHandler;
import me.caseload.knockbacksync.util.CustomUpdateIntervals;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.impl.FabricLoaderImpl;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerEntity;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
@@ -9,32 +18,65 @@ import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
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.Map;
//
//@Mixin(ServerEntity.class)
//public class ServerEntityMixin implements UpdateIntervalAccessor {
@Mixin(ServerEntity.class)
public class ServerEntityMixin {
@Shadow
@Final
private Entity entity;
@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) {
Vec3 velocity = player.getDeltaMovement();
InteractionResult result = PlayerVelocityEvent.EVENT.invoker().onVelocityChange(player, velocity);
if (result == InteractionResult.FAIL) {
this.entity.hurtMarked = false;
ci.cancel();
} else if (result == InteractionResult.SUCCESS) {
// Currently unnecessary since we do this in the handler, will move later
player.setDeltaMovement(velocity);
}
}
}
}
//
// @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) {
// Vec3 velocity = player.getDeltaMovement();
//
// InteractionResult result = PlayerVelocityEvent.EVENT.invoker().onVelocityChange(player, velocity);
//
// if (result == InteractionResult.FAIL) {
// this.entity.hurtMarked = false;
// ci.cancel();
// } else if (result == InteractionResult.SUCCESS) {
// // Currently unnecessary since we do this in the handler, will move later
// player.setDeltaMovement(velocity);
// }
// }
// }
//
// 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;
// }
//
//
// @KBSyncEventHandler
// public void onConfigReload(ConfigReloadEvent event) {
// Map<String, Integer> entityIntervals = CustomUpdateIntervals.loadEntityIntervalsFromConfig();
// for (ServerLevel level : KBSyncFabricLoaderMod.getServer().getAllLevels()) {
// CustomUpdateIntervals.updateIntervals(level, entityIntervals);
// }
// }
//}
@@ -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;
@@ -11,6 +13,7 @@ import net.minecraft.server.level.ServerPlayer;
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;
@@ -81,9 +84,8 @@ public class FabricPlayer implements PlatformPlayer {
}
@Override
public Vector3d getLocation() {
Vec3 pos = fabricPlayer.position();
return new Vector3d(pos.x, pos.y, pos.z);
public Location getLocation() {
return new Location(fabricPlayer.getX(), fabricPlayer.getY(), fabricPlayer.getZ(), fabricPlayer.getYRot(), fabricPlayer.getXRot());
}
@Override
@@ -125,5 +127,11 @@ public class FabricPlayer implements PlatformPlayer {
fabricPlayer.hurtMarked = true;
}
@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
}
@@ -0,0 +1,58 @@
package me.caseload.knockbacksync.util;
import me.caseload.knockbacksync.ConfigWrapper;
import me.caseload.knockbacksync.KnockbackSyncBase;
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 = KnockbackSyncBase.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;
}
}
+2 -1
View File
@@ -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,10 +4,12 @@
"package": "me.caseload.knockbacksync.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [
"ChunkMapMixin",
"CommandStackSourceAccessor",
"PlayerMixin",
"ServerEntityMixin",
"TickRateManagerMixin"
// "ServerEntityMixin",
"TickRateManagerMixin",
// "TrackedEntityMixin"
],
"injectors": {
"defaultRequire": 1
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