This commit is contained in:
YuruzukiOvO
2025-11-21 22:32:04 +08:00
commit 4aa069ca79
22 changed files with 1694 additions and 0 deletions

38
.gitignore vendored Normal file
View File

@@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# 默认忽略的文件
/shelf/
/workspace.xml

7
.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>

14
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="azul-1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

BIN
libs/ProtocolLib.jar Normal file

Binary file not shown.

Binary file not shown.

45
pom.xml Normal file
View File

@@ -0,0 +1,45 @@
<?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.dw1e.kbm</groupId>
<artifactId>KnockBackManager</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.github.spigot</groupId>
<artifactId>1.8.8</artifactId>
<version>1.8.8</version>
<scope>system</scope>
<systemPath>${basedir}/libs/spigot-1.8.8-R0.1-SNAPSHOT-latest.jar</systemPath>
</dependency>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>5.1.0</version>
<scope>system</scope>
<systemPath>${basedir}/libs/ProtocolLib.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,101 @@
package me.dw1e.kbm;
import me.dw1e.kbm.api.KnockbackManagerAPI;
import me.dw1e.kbm.commands.KBMCommandHandler;
import me.dw1e.kbm.config.ConfigManager;
import me.dw1e.kbm.data.PlayerDataManager;
import me.dw1e.kbm.listeners.PacketListener;
import me.dw1e.kbm.listeners.PlayerEventListener;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.plugin.java.JavaPlugin;
public final class KnockbackManager extends JavaPlugin {
private static KnockbackManager instance;
private ConfigManager configManager;
private PlayerDataManager playerDataManager;
private KnockbackManagerAPI api;
private PacketListener packetListener;
private int serverTick;
private final String pluginPrefix = ChatColor.DARK_GRAY + "[" + ChatColor.YELLOW + "KBM" + ChatColor.DARK_GRAY + "] ";
@Override
public void onEnable() {
instance = this;
if (!getDescription().getName().equals("KnockbackManager") ||
!getDescription().getAuthors().contains("dw1e")) {
getLogger().severe("检测到插件信息篡改,已停止运行!");
Bukkit.getPluginManager().disablePlugin(this);
return;
}
this.configManager = new ConfigManager(this);
this.playerDataManager = new PlayerDataManager();
this.api = new KnockbackManagerAPIImpl(this);
this.configManager.loadConfigs(Bukkit.getConsoleSender());
this.playerDataManager.registerAllPlayers();
getCommand("kbm").setExecutor(new KBMCommandHandler(this));
Bukkit.getPluginManager().registerEvents(new PlayerEventListener(this), this);
Bukkit.getScheduler().runTaskTimer(this, () -> serverTick++, 0L, 1L);
setupProtocolLib();
getLogger().info("Knockback Manager 已启用!");
}
@Override
public void onDisable() {
if (packetListener != null) {
packetListener.unregister();
}
if (playerDataManager != null) {
playerDataManager.clearAllData();
}
instance = null;
getLogger().info("Knockback Manager 已禁用!");
}
private void setupProtocolLib() {
org.bukkit.plugin.Plugin protocolLib = Bukkit.getPluginManager().getPlugin("ProtocolLib");
if (protocolLib != null && protocolLib.isEnabled()) {
String version = protocolLib.getDescription().getVersion();
try {
int minorVersion = Integer.parseInt(version.split("\\.")[1]);
if (minorVersion < 5) {
logMessage(ChatColor.RED + "不支持的 ProtocolLib 版本: " + ChatColor.YELLOW + version +
ChatColor.RED + ", 请使用 5.0.0 或之后的版本, Misplace 模块将不会启用!");
} else {
logMessage(ChatColor.GREEN + "检测到 ProtocolLib " + ChatColor.YELLOW + version +
ChatColor.GREEN + ", 已启用 Misplace 模块!");
this.packetListener = new PacketListener(this);
this.packetListener.register();
}
} catch (Exception e) {
logMessage(ChatColor.RED + "解析 ProtocolLib 版本时出错, Misplace 模块将不会启用!");
}
} else {
logMessage(ChatColor.RED + "未检测到 ProtocolLib 安装/运行, Misplace 模块将不会启用!");
}
}
public void logMessage(String message) {
Bukkit.getConsoleSender().sendMessage(pluginPrefix + message);
}
// Getters
public static KnockbackManager getInstance() { return instance; }
public ConfigManager getConfigManager() { return configManager; }
public PlayerDataManager getPlayerDataManager() { return playerDataManager; }
public KnockbackManagerAPI getAPI() { return api; }
public int getServerTick() { return serverTick; }
public String getPluginPrefix() { return pluginPrefix; }
}

View File

@@ -0,0 +1,68 @@
package me.dw1e.kbm;
import me.dw1e.kbm.api.KnockbackManagerAPI;
import me.dw1e.kbm.config.ConfigHolder;
import me.dw1e.kbm.data.PlayerKnockbackData;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
public class KnockbackManagerAPIImpl implements KnockbackManagerAPI {
private final KnockbackManager plugin;
public KnockbackManagerAPIImpl(KnockbackManager plugin) {
this.plugin = plugin;
}
@Override
public boolean isMisplacing(Player player) {
PlayerKnockbackData data = getPlayerData(player);
FileConfiguration config = getKBConfig(data.getCurrentKBFile());
return config != null &&
plugin.getServerTick() - data.getLastHitTick() < config.getInt("misplace.delay");
}
@Override
public boolean isFilter(Player player) {
return getPlayerData(player).isFiltered();
}
@Override
public FileConfiguration getKBConfig(String configName) {
if (!isKBFileExist(configName)) {
return null;
}
ConfigHolder holder = plugin.getConfigManager().getConfigs().get(configName);
return (FileConfiguration) holder.getConfig();
}
@Override
public String getKBFile(Player player) {
return getPlayerData(player).getCurrentKBFile();
}
@Override
public boolean setKBFile(Player player, String configName) {
if (!isKBFileExist(configName)) {
return false;
}
getPlayerData(player).setCurrentKBFile(configName);
return true;
}
@Override
public boolean isKBFileExist(String configName) {
return plugin.getConfigManager().getConfigs().containsKey(configName);
}
@Override
public void setFilter(Player player, boolean filtered) {
getPlayerData(player).setFiltered(filtered);
}
private PlayerKnockbackData getPlayerData(Player player) {
return plugin.getPlayerDataManager().getPlayerData(player.getUniqueId());
}
}

View File

@@ -0,0 +1,46 @@
package me.dw1e.kbm.api;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.util.Vector;
public class KBMPlayerVelocityEvent extends PlayerEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private Vector velocity;
private boolean cancelled;
public KBMPlayerVelocityEvent(Player player, Vector velocity) {
super(player);
this.velocity = velocity;
this.cancelled = false;
}
@Override
public boolean isCancelled() {
return this.cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
public Vector getVelocity() {
return this.velocity;
}
public void setVelocity(Vector velocity) {
this.velocity = velocity;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,14 @@
package me.dw1e.kbm.api;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
public interface KnockbackManagerAPI {
boolean isMisplacing(Player player);
boolean isFilter(Player player);
FileConfiguration getKBConfig(String configName);
String getKBFile(Player player);
boolean setKBFile(Player player, String configName);
boolean isKBFileExist(String configName);
void setFilter(Player player, boolean filtered);
}

View File

@@ -0,0 +1,491 @@
package me.dw1e.kbm.commands;
import me.dw1e.kbm.KnockbackManager;
import me.dw1e.kbm.config.ConfigHolder;
import me.dw1e.kbm.data.PlayerKnockbackData;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
public class KBMCommandHandler implements TabExecutor {
private final KnockbackManager plugin;
private final List<String> editableConfigPaths;
public KBMCommandHandler(KnockbackManager plugin) {
this.plugin = plugin;
this.editableConfigPaths = Arrays.asList(
"horizontal.ground", "horizontal.air", "horizontal.sprint_extra",
"horizontal.projectile_multiplier", "vertical.ground", "vertical.air",
"vertical.sprint_extra", "vertical.projectile_multiplier",
"misplace.enabled", "misplace.delay", "stop_sprint", "y_limit", "hit_delay"
);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!sender.hasPermission("kbm.use")) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED + "你没有此命令的使用权限!");
return true;
}
if (args.length == 0) {
sendHelp(sender, label);
return true;
}
String subCommand = args[0].toLowerCase();
switch (subCommand) {
case "help":
sendHelp(sender, label);
break;
case "create":
handleCreate(sender, args, label);
break;
case "delete":
handleDelete(sender, args, label);
break;
case "list":
handleList(sender);
break;
case "edit":
handleEdit(sender, args, label);
break;
case "view":
handleView(sender, args, label);
break;
case "reload":
handleReload(sender, args, label);
break;
case "getkb":
handleGetKB(sender, args, label);
break;
case "setkb":
handleSetKB(sender, args, label);
break;
case "filter":
handleFilter(sender, args, label);
break;
default:
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED + "未知子命令: " + args[0]);
break;
}
return true;
}
private void sendHelp(CommandSender sender, String label) {
String commandBase = ChatColor.WHITE + " /" + label;
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.GRAY + "可用命令:");
sender.sendMessage(commandBase + " create" + ChatColor.GRAY + ": 创建KB文件");
sender.sendMessage(commandBase + " delete" + ChatColor.GRAY + ": 删除KB文件");
sender.sendMessage(commandBase + " list" + ChatColor.GRAY + ": 查看已读取的KB文件");
sender.sendMessage(commandBase + " edit" + ChatColor.GRAY + ": 编辑KB文件的数值");
sender.sendMessage(commandBase + " view" + ChatColor.GRAY + ": 查看KB文件的数值");
sender.sendMessage(commandBase + " reload" + ChatColor.GRAY + ": 重新加载KB文件");
sender.sendMessage(commandBase + " getkb" + ChatColor.GRAY + ": 查看玩家使用的KB文件");
sender.sendMessage(commandBase + " setkb" + ChatColor.GRAY + ": 设置玩家使用的KB文件");
sender.sendMessage(commandBase + " filter" + ChatColor.GRAY + ": 过滤玩家");
sender.sendMessage(ChatColor.DARK_GREEN + "使用教程&参考配置: 请见群(673765463)文件");
}
private void handleCreate(CommandSender sender, String[] args, String label) {
if (args.length != 2) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"用法: /" + label + " create <KB文件名>");
return;
}
plugin.getConfigManager().createConfig(args[1], sender);
}
private void handleDelete(CommandSender sender, String[] args, String label) {
if (args.length != 2) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"用法: /" + label + " delete <KB文件名>");
return;
}
String configName = args[1];
if (configName.equals("default")) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED + "默认KB文件不允许删除!");
return;
}
if (!plugin.getConfigManager().getConfigs().containsKey(configName)) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED + "KB文件 " + configName + " 不存在!");
return;
}
plugin.getPlayerDataManager().getAllData().values().stream()
.filter(data -> data.getCurrentKBFile().equals(configName))
.forEach(data -> data.setCurrentKBFile("default"));
plugin.getConfigManager().getConfigs().remove(configName);
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.GREEN + "删除 " + configName + " 成功!");
}
private void handleList(CommandSender sender) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.GRAY + "已读取的KB文件:");
plugin.getConfigManager().getConfigs().keySet().forEach(configName ->
sender.sendMessage(" " + ChatColor.WHITE + configName)
);
}
private void handleEdit(CommandSender sender, String[] args, String label) {
if (args.length != 4) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"用法: /" + label + " edit <KB文件名> <配置路径> <数值>");
return;
}
String configName = args[1];
String configPath = args[2].toLowerCase();
String value = args[3];
if (!plugin.getConfigManager().getConfigs().containsKey(configName)) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED + "KB文件 " + configName + " 不存在!");
return;
}
if (!editableConfigPaths.contains(configPath)) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED + "无效的配置路径: " + configPath);
return;
}
ConfigHolder holder = plugin.getConfigManager().getConfigs().get(configName);
FileConfiguration config = (FileConfiguration) holder.getConfig();
try {
Object oldValue = config.get(configPath);
Object newValue = parseValue(configPath, value);
if (oldValue != null && oldValue.equals(newValue)) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"无变化. " + configName + " 中的 " + configPath + " 原已设置为 " + oldValue);
return;
}
config.set(configPath, newValue);
config.save(holder.getFile());
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.GRAY +
"已将 " + ChatColor.WHITE + configName + ChatColor.GRAY + " 中的 " +
ChatColor.WHITE + configPath + ChatColor.GRAY + " 设置为 " +
ChatColor.WHITE + newValue);
} catch (Exception e) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"设置配置值时发生错误: " + e.getMessage());
e.printStackTrace();
}
}
private void handleView(CommandSender sender, String[] args, String label) {
if (args.length != 2) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"用法: /" + label + " view <KB文件名>");
return;
}
String configName = args[1];
if (!plugin.getConfigManager().getConfigs().containsKey(configName)) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED + "KB文件 " + configName + " 不存在!");
return;
}
ConfigHolder holder = plugin.getConfigManager().getConfigs().get(configName);
FileConfiguration config = (FileConfiguration) holder.getConfig();
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.GRAY +
"查看KB文件 " + ChatColor.WHITE + configName + ChatColor.GRAY + " 的数值:");
StringBuilder configDisplay = new StringBuilder();
formatConfigSection(configDisplay, config, 1);
String[] lines = configDisplay.toString().split("\n");
for (String line : lines) {
sender.sendMessage(ChatColor.YELLOW + line);
}
}
private void handleReload(CommandSender sender, String[] args, String label) {
if (args.length != 2) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"用法: /" + label + " reload <KB文件名|*>");
return;
}
String configName = args[1];
if (configName.equals("*")) {
plugin.getConfigManager().loadConfigs(sender);
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.GREEN + "已重载所有KB文件!");
} else {
plugin.getConfigManager().reloadConfig(configName, sender);
}
}
private void handleGetKB(CommandSender sender, String[] args, String label) {
if (args.length != 2) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"用法: /" + label + " getkb <玩家>");
return;
}
Player player = Bukkit.getPlayerExact(args[1]);
if (player == null) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"没有找到名为 " + args[1] + " 的玩家!");
return;
}
PlayerKnockbackData data = plugin.getPlayerDataManager().getPlayerData(player.getUniqueId());
if (data == null) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"无法获取玩家 " + player.getName() + " 的数据!");
return;
}
if (data.isFiltered()) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
player.getName() + " 当前位于过滤名单中!");
} else {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.WHITE +
player.getName() + ChatColor.GRAY + " 当前使用的KB为: " +
ChatColor.WHITE + data.getCurrentKBFile());
}
}
private void handleSetKB(CommandSender sender, String[] args, String label) {
if (args.length != 3) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"用法: /" + label + " setkb <玩家|*> <KB文件名>");
return;
}
String playerName = args[1];
String configName = args[2];
if (!plugin.getConfigManager().getConfigs().containsKey(configName)) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED + "KB文件 " + configName + " 不存在!");
return;
}
if (playerName.equals("*")) {
// 设置所有玩家
plugin.getPlayerDataManager().getAllData().values().forEach(data ->
data.setCurrentKBFile(configName)
);
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.GRAY +
"已将所有玩家使用的KB设置为 " + ChatColor.WHITE + configName);
} else {
// 设置单个玩家
Player player = Bukkit.getPlayerExact(playerName);
if (player == null) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"没有找到名为 " + playerName + " 的玩家!");
return;
}
PlayerKnockbackData data = plugin.getPlayerDataManager().getPlayerData(player.getUniqueId());
if (data == null) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"无法获取玩家 " + player.getName() + " 的数据!");
return;
}
if (data.getCurrentKBFile().equals(configName)) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"无变化. " + player.getName() + " 原已使用 " + configName);
return;
}
data.setCurrentKBFile(configName);
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.GRAY +
"已将 " + ChatColor.WHITE + player.getName() + ChatColor.GRAY +
" 使用的KB设置为 " + ChatColor.WHITE + configName);
}
}
private void handleFilter(CommandSender sender, String[] args, String label) {
if (args.length != 3) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"用法: /" + label + " filter <玩家> <true/false>");
return;
}
Player player = Bukkit.getPlayerExact(args[1]);
if (player == null) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"没有找到名为 " + args[1] + " 的玩家!");
return;
}
String filterValue = args[2].toLowerCase();
if (!filterValue.equals("true") && !filterValue.equals("false")) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"无效的布尔值: " + args[2]);
return;
}
boolean filtered = Boolean.parseBoolean(filterValue);
PlayerKnockbackData data = plugin.getPlayerDataManager().getPlayerData(player.getUniqueId());
if (data == null) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"无法获取玩家 " + player.getName() + " 的数据!");
return;
}
if (data.isFiltered() == filtered) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"无变化. " + player.getName() + " 原已" + (filtered ? "加入" : "移出") + "过滤名单!");
return;
}
data.setFiltered(filtered);
String action = filtered ? ChatColor.GREEN + "加入" : ChatColor.RED + "移出";
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.GRAY +
"已将 " + ChatColor.WHITE + player.getName() + " " + action +
ChatColor.GRAY + "过滤名单!");
}
private Object parseValue(String configPath, String value) {
// 处理布尔值
if (configPath.equals("misplace.enabled") || configPath.equals("stop_sprint")) {
if (value.equalsIgnoreCase("true") || value.equals("1")) {
return true;
} else if (value.equalsIgnoreCase("false") || value.equals("0")) {
return false;
}
throw new IllegalArgumentException("无效的布尔值: " + value);
}
if (configPath.equals("misplace.delay") || configPath.equals("hit_delay")) {
try {
int intValue = Integer.parseInt(value);
if (configPath.equals("misplace.delay") && (intValue < 1 || intValue > 5)) {
throw new IllegalArgumentException("misplace.delay 必须在 1-5 范围内");
}
return intValue;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("无效的整数值: " + value);
}
}
try {
return Double.parseDouble(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("无效的数值: " + value);
}
}
private void formatConfigSection(StringBuilder builder, ConfigurationSection section, int depth) {
StringBuilder indent = new StringBuilder();
for (int i = 0; i < depth; i++) {
indent.append(" ");
}
for (String key : section.getKeys(false)) {
if (section.isConfigurationSection(key)) {
builder.append(ChatColor.YELLOW).append(indent).append(key)
.append(ChatColor.WHITE).append(":\n");
formatConfigSection(builder, section.getConfigurationSection(key), depth + 1);
} else {
Object value = section.get(key);
builder.append(ChatColor.GRAY).append(indent).append(key)
.append(ChatColor.WHITE).append(": ").append(value).append("\n");
}
}
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
List<String> completions = new ArrayList<>();
if (!sender.hasPermission("kbm.use")) return completions;
if (args.length == 1) {
return filterCompletions(Arrays.asList(
"create", "delete", "list", "edit", "view", "reload", "getkb", "setkb", "filter", "help"
), args[0]);
}
List<String> configNames = new ArrayList<>(plugin.getConfigManager().getConfigs().keySet());
List<String> playerNames = Bukkit.getOnlinePlayers().stream()
.map(Player::getName)
.collect(Collectors.toList());
String subCommand = args[0].toLowerCase();
switch (subCommand) {
case "delete":
case "edit":
case "view":
case "reload":
if (args.length == 2) {
return filterCompletions(configNames, args[1]);
}
if (args.length == 3 && subCommand.equals("edit")) {
return filterCompletions(editableConfigPaths, args[2]);
}
if (args.length == 4 && subCommand.equals("edit")) {
String configPath = args[2].toLowerCase();
if (configPath.equals("misplace.enabled") || configPath.equals("stop_sprint")) {
return filterCompletions(Arrays.asList("true", "false"), args[3]);
} else if (configPath.equals("misplace.delay")) {
return filterCompletions(Arrays.asList("1", "2", "3", "4", "5"), args[3]);
}
}
break;
case "getkb":
case "setkb":
case "filter":
if (args.length == 2) {
List<String> allOptions = new ArrayList<>(playerNames);
allOptions.add("*");
return filterCompletions(allOptions, args[1]);
}
if (args.length == 3) {
if (subCommand.equals("filter")) {
return filterCompletions(Arrays.asList("true", "false"), args[2]);
} else if (subCommand.equals("setkb")) {
return filterCompletions(configNames, args[2]);
}
}
break;
case "create":
if (args.length == 2) {
return filterCompletions(Arrays.asList("custom", "new", "test"), args[1]);
}
break;
}
return completions;
}
private List<String> filterCompletions(List<String> options, String input) {
return options.stream()
.filter(option -> option.toLowerCase().startsWith(input.toLowerCase()))
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,21 @@
package me.dw1e.kbm.config;
import java.io.File;
public class ConfigHolder {
private final File configFile;
private final Object config;
public ConfigHolder(File configFile, Object config) {
this.configFile = configFile;
this.config = config;
}
public File getFile() {
return this.configFile;
}
public Object getConfig() {
return this.config;
}
}

View File

@@ -0,0 +1,242 @@
package me.dw1e.kbm.config;
import me.dw1e.kbm.KnockbackManager;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class ConfigManager {
private final KnockbackManager plugin;
private final Map<String, ConfigHolder> configs;
private final FileConfiguration defaultConfig;
public ConfigManager(KnockbackManager plugin) {
this.plugin = plugin;
this.configs = new HashMap<>();
this.defaultConfig = YamlConfiguration.loadConfiguration(
new InputStreamReader(plugin.getResource("default.yml"))
);
}
public void loadConfigs(CommandSender sender) {
if (!plugin.getDataFolder().exists()) {
plugin.getDataFolder().mkdirs();
}
File defaultFile = new File(plugin.getDataFolder(), "default.yml");
if (!defaultFile.exists()) {
plugin.saveResource("default.yml", false);
}
configs.clear();
try {
Files.list(Paths.get(plugin.getDataFolder().getPath()))
.filter(path -> Files.isRegularFile(path) && path.toString().endsWith(".yml"))
.forEach(path -> {
String fileName = path.getFileName().toString().replace(".yml", "");
File file = path.toFile();
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
configs.put(fileName, new ConfigHolder(file, config));
updateConfigValues(fileName, config, sender);
try {
config.save(file);
} catch (IOException e) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"保存 " + fileName + " 失败!");
e.printStackTrace();
}
});
} catch (IOException e) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"读取配置文件时发生错误!");
e.printStackTrace();
}
}
private void updateConfigValues(String configName, FileConfiguration config, CommandSender sender) {
if (config.get("ground_horizontal") != null) {
plugin.logMessage(ChatColor.GREEN + "已将旧版配置文件 " + configName + " 更新!");
migrateOldConfig(config);
}
config.options().copyDefaults(true).copyHeader(true);
config.addDefaults(defaultConfig);
config.set("misplace.delay", Math.max(1, Math.min(5, config.getInt("misplace.delay"))));
}
private void migrateOldConfig(FileConfiguration config) {
config.set("horizontal.ground", config.get("ground_horizontal"));
config.set("horizontal.air", config.get("air_horizontal"));
config.set("horizontal.sprint_extra", config.get("extra_horizontal"));
config.set("horizontal.projectile_multiplier", config.get("projectile_multiplier_horizontal"));
config.set("vertical.ground", config.get("ground_vertical"));
config.set("vertical.air", config.get("air_vertical"));
config.set("vertical.sprint_extra", config.get("extra_vertical"));
config.set("vertical.projectile_multiplier", config.get("projectile_multiplier_vertical"));
String[] oldKeys = {
"ground_horizontal", "air_horizontal", "extra_horizontal", "projectile_multiplier_horizontal",
"ground_vertical", "air_vertical", "extra_vertical", "projectile_multiplier_vertical"
};
for (String key : oldKeys) {
config.set(key, null);
}
}
public Map<String, ConfigHolder> getConfigs() {
return configs;
}
public void createConfig(String name, CommandSender sender) {
if (configs.containsKey(name)) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"配置文件 " + name + " 已存在!");
return;
}
File file = new File(plugin.getDataFolder(), name + ".yml");
try {
if (!file.createNewFile()) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"创建配置文件 " + name + " 失败!");
return;
}
} catch (IOException e) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"创建配置文件 " + name + " 失败!");
e.printStackTrace();
return;
}
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
config.options().copyDefaults(true).copyHeader(true);
config.addDefaults(defaultConfig);
try {
config.save(file);
} catch (IOException e) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"保存配置文件 " + name + " 失败!");
e.printStackTrace();
return;
}
configs.put(name, new ConfigHolder(file, config));
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.GREEN +
"创建配置文件 " + name + " 成功!");
}
public void reloadConfig(String name, CommandSender sender) {
if (!configs.containsKey(name)) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"配置文件 " + name + " 不存在!");
return;
}
ConfigHolder holder = configs.get(name);
try {
((FileConfiguration) holder.getConfig()).load(holder.getFile());
updateConfigValues(name, (FileConfiguration) holder.getConfig(), sender);
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.GREEN +
"重载配置文件 " + name + " 成功!");
} catch (Exception e) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED +
"重载配置文件 " + name + " 失败!");
e.printStackTrace();
}
}
public void deleteConfig(String name, CommandSender sender) {
if (name.equals("default")) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED + "默认KB文件不允许删除!");
return;
}
if (!configs.containsKey(name)) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED + "KB文件 " + name + " 不存在!");
return;
}
plugin.getPlayerDataManager().getAllData().values().stream()
.filter(data -> data.getCurrentKBFile().equals(name))
.forEach(data -> data.setCurrentKBFile("default"));
ConfigHolder holder = configs.get(name);
File configFile = holder.getFile();
boolean deleted = configFile.delete();
configs.remove(name);
if (deleted) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.GREEN + "删除 " + name + " 成功!");
} else {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED + "删除 " + name + " 失败!");
}
}
public void saveConfig(String name, CommandSender sender) {
if (!configs.containsKey(name)) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED + "配置文件 " + name + " 不存在!");
return;
}
ConfigHolder holder = configs.get(name);
try {
((FileConfiguration) holder.getConfig()).save(holder.getFile());
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.GREEN + "保存配置文件 " + name + " 成功!");
} catch (IOException e) {
sender.sendMessage(plugin.getPluginPrefix() + ChatColor.RED + "保存配置文件 " + name + " 失败!");
e.printStackTrace();
}
}
private void validateConfigValues(String configName, FileConfiguration config) {
int misplaceDelay = config.getInt("misplace.delay");
if (misplaceDelay < 1 || misplaceDelay > 5) {
plugin.logMessage(ChatColor.YELLOW + "配置 " + configName + " 中的 misplace.delay 超出范围 (1-5),已自动修正");
config.set("misplace.delay", Math.max(1, Math.min(5, misplaceDelay)));
}
String[] requiredPaths = {
"horizontal.ground", "horizontal.air", "vertical.ground", "vertical.air"
};
for (String path : requiredPaths) {
if (!config.contains(path)) {
plugin.logMessage(ChatColor.YELLOW + "配置 " + configName + " 缺少必需字段: " + path + ",使用默认值");
config.set(path, defaultConfig.get(path));
}
}
}
public FileConfiguration getDefaultConfig() {
return this.defaultConfig;
}
}

View File

@@ -0,0 +1,40 @@
package me.dw1e.kbm.data;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class PlayerDataManager {
private final Map<UUID, PlayerKnockbackData> playerData;
public PlayerDataManager() {
this.playerData = new ConcurrentHashMap<>();
}
public void registerAllPlayers() {
Bukkit.getOnlinePlayers().forEach(this::registerPlayer);
}
public void registerPlayer(Player player) {
playerData.putIfAbsent(player.getUniqueId(), new PlayerKnockbackData(player));
}
public void unregisterPlayer(UUID uuid) {
playerData.remove(uuid);
}
public PlayerKnockbackData getPlayerData(UUID uuid) {
return playerData.get(uuid);
}
public Map<UUID, PlayerKnockbackData> getAllData() {
return playerData;
}
public void clearAllData() {
playerData.clear();
}
}

View File

@@ -0,0 +1,103 @@
package me.dw1e.kbm.data;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
public class PlayerKnockbackData {
private Vector pendingVelocity;
private boolean isOnGround;
private boolean isFiltered;
private String currentKBFile;
private int lastAttackerId;
private double lastGroundY;
private final Player player;
private int lastHitTick;
private int hitDelay;
private int serverTick;
public void setServerTick(int serverTick) {
this.serverTick = serverTick;
}
public int getLastAttackerId() {
return this.lastAttackerId;
}
public int getLastHitTick() {
return this.lastHitTick;
}
public int getHitDelay() {
return this.hitDelay;
}
public boolean isOnGround() {
return this.isOnGround;
}
public boolean isFiltered() {
return this.isFiltered;
}
public void setHitDelay(int hitDelay) {
this.hitDelay = hitDelay;
}
public void setLastHitTick(int lastHitTick) {
this.lastHitTick = lastHitTick;
}
public void setPendingVelocity(Vector velocity) {
this.pendingVelocity = velocity;
}
public int getServerTick() {
return this.serverTick;
}
public Vector getPendingVelocity() {
return this.pendingVelocity;
}
public void setLastAttackerId(int lastAttackerId) {
this.lastAttackerId = lastAttackerId;
}
public double getLastGroundY() {
return this.lastGroundY;
}
public void setLastGroundY(double lastGroundY) {
this.lastGroundY = lastGroundY;
}
public boolean checkOnGround() {
return this.player.isOnGround();
}
public void setSprinting(boolean sprinting) {
this.isOnGround = sprinting;
}
public PlayerKnockbackData(Player player) {
this.currentKBFile = "default";
this.pendingVelocity = null;
this.lastAttackerId = -1;
this.player = player;
this.lastGroundY = player.getLocation().getY();
}
public String getCurrentKBFile() {
return this.currentKBFile;
}
public void setFiltered(boolean filtered) {
this.isFiltered = filtered;
// 设置无敌时间
this.player.setMaximumNoDamageTicks(20);
}
public void setCurrentKBFile(String kbFile) {
this.currentKBFile = kbFile;
}
}

View File

@@ -0,0 +1,158 @@
package me.dw1e.kbm.knockback;
import me.dw1e.kbm.KnockbackManager;
import me.dw1e.kbm.api.KBMPlayerVelocityEvent;
import me.dw1e.kbm.config.ConfigHolder;
import me.dw1e.kbm.data.PlayerKnockbackData;
import me.dw1e.kbm.data.PlayerDataManager;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.util.Vector;
public class KnockbackCalculator {
private final KnockbackManager plugin;
public KnockbackCalculator(KnockbackManager plugin) {
this.plugin = plugin;
}
public void calculateKnockback(Player victim, Player attacker,
PlayerKnockbackData victimData, Entity damager) {
PlayerKnockbackData attackerData = plugin.getPlayerDataManager()
.getPlayerData(attacker.getUniqueId());
if (attackerData == null) return;
int currentTick = plugin.getServerTick();
victimData.setLastAttackerId(attacker.getEntityId());
victimData.setHitDelay(currentTick);
attackerData.setLastHitTick(currentTick);
FileConfiguration config = getVictimConfig(victimData);
if (config == null) return;
Vector knockback = calculateBaseKnockback(victim, attacker, config, damager);
applyKnockbackModifiers(knockback, victim, attacker, config, damager, victimData);
KBMPlayerVelocityEvent velocityEvent = new KBMPlayerVelocityEvent(victim, knockback);
Bukkit.getPluginManager().callEvent(velocityEvent);
if (!velocityEvent.isCancelled()) {
int hitDelay = config.getInt("hit_delay");
if (victim.getMaximumNoDamageTicks() != hitDelay) {
victim.setMaximumNoDamageTicks(hitDelay);
}
victimData.setPendingVelocity(velocityEvent.getVelocity());
}
}
private FileConfiguration getVictimConfig(PlayerKnockbackData victimData) {
ConfigHolder holder = plugin.getConfigManager().getConfigs()
.get(victimData.getCurrentKBFile());
return holder != null ? (FileConfiguration) holder.getConfig() : null;
}
private Vector calculateBaseKnockback(Player victim, Player attacker,
FileConfiguration config, Entity damager) {
Vector direction = calculateDirectionVector(victim, attacker);
boolean victimOnGround = victim.isOnGround();
double horizontal = victimOnGround ?
config.getDouble("horizontal.ground") : config.getDouble("horizontal.air");
double vertical = victimOnGround ?
config.getDouble("vertical.ground") : config.getDouble("vertical.air");
Vector baseKnockback = direction.multiply(new Vector(horizontal, vertical, horizontal));
if (damager instanceof Projectile) {
double projectileMultiplier = config.getDouble("horizontal.projectile_multiplier");
baseKnockback.multiply(new Vector(projectileMultiplier,
config.getDouble("vertical.projectile_multiplier"), projectileMultiplier));
}
return baseKnockback;
}
private Vector calculateDirectionVector(Player victim, Player attacker) {
double x = victim.getLocation().getX() - attacker.getLocation().getX();
double z = victim.getLocation().getZ() - attacker.getLocation().getZ();
if (Math.hypot(x, z) < 1.0E-4D) {
x = (Math.random() - Math.random()) * 0.01D;
z = (Math.random() - Math.random()) * 0.01D;
}
float attackerYaw = attacker.getLocation().getYaw() * 3.1415927F / 180.0F;
return new Vector(-Math.sin(attackerYaw), 0.0D, Math.cos(attackerYaw))
.normalize().setY(1.0D);
}
private void applyKnockbackModifiers(Vector knockback, Player victim, Player attacker,
FileConfiguration config, Entity damager,
PlayerKnockbackData victimData) {
if (!(damager instanceof Projectile)) {
applyKnockbackEnchantment(knockback, attacker, config);
} else if (damager instanceof Arrow) {
applyArrowKnockback(knockback, (Arrow) damager, config);
}
double yLimit = config.getDouble("y_limit");
if (victim.getLocation().getY() - victimData.getLastGroundY() > yLimit) {
knockback.setY(0.0D);
}
}
private void applyKnockbackEnchantment(Vector knockback, Player attacker,
FileConfiguration config) {
int knockbackLevel = attacker.getItemInHand().getEnchantmentLevel(Enchantment.KNOCKBACK);
boolean stopSprint = config.getBoolean("stop_sprint");
PlayerKnockbackData attackerData = plugin.getPlayerDataManager()
.getPlayerData(attacker.getUniqueId());
if ((!stopSprint && attackerData.isOnGround()) ||
(stopSprint && attacker.isSprinting())) {
knockbackLevel++;
}
if (knockbackLevel > 0) {
float attackerYaw = attacker.getLocation().getYaw() * 3.1415927F / 180.0F;
double sprintExtra = config.getDouble("horizontal.sprint_extra");
double verticalExtra = config.getDouble("vertical.sprint_extra");
Vector extraKnockback = new Vector(
-Math.sin(attackerYaw) * knockbackLevel * sprintExtra,
verticalExtra,
Math.cos(attackerYaw) * knockbackLevel * sprintExtra
);
knockback.add(extraKnockback);
}
}
private void applyArrowKnockback(Vector knockback, Arrow arrow, FileConfiguration config) {
int arrowKnockback = arrow.getKnockbackStrength();
if (arrowKnockback > 0) {
double knockbackStrength = arrowKnockback * 0.6;
double horizontalMagnitude = Math.hypot(knockback.getX(), knockback.getZ());
Vector extraKnockback = new Vector(
knockback.getX() * knockbackStrength / horizontalMagnitude,
config.getDouble("vertical.sprint_extra"),
knockback.getZ() * knockbackStrength / horizontalMagnitude
);
knockback.add(extraKnockback);
}
}
}

View File

@@ -0,0 +1,106 @@
package me.dw1e.kbm.listeners;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import me.dw1e.kbm.KnockbackManager;
import me.dw1e.kbm.config.ConfigHolder;
import me.dw1e.kbm.data.PlayerKnockbackData;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import java.util.Arrays;
import java.util.List;
public class PacketListener extends PacketAdapter {
private final KnockbackManager plugin;
private static final List<PacketType> LISTENED_PACKETS = Arrays.asList(
PacketType.Play.Server.ENTITY_TELEPORT,
PacketType.Play.Server.REL_ENTITY_MOVE_LOOK,
PacketType.Play.Server.REL_ENTITY_MOVE,
PacketType.Play.Server.ENTITY_LOOK
);
public PacketListener(KnockbackManager plugin) {
super(plugin, ListenerPriority.NORMAL, LISTENED_PACKETS);
this.plugin = plugin;
}
public void register() {
ProtocolLibrary.getProtocolManager().addPacketListener(this);
}
public void unregister() {
ProtocolLibrary.getProtocolManager().removePacketListener(this);
}
@Override
public void onPacketSending(PacketEvent event) {
if (!LISTENED_PACKETS.contains(event.getPacketType())) return;
Player player = event.getPlayer();
PlayerKnockbackData data = plugin.getPlayerDataManager().getPlayerData(player.getUniqueId());
if (data == null) return;
FileConfiguration config = getPlayerConfig(data);
if (config == null || !config.getBoolean("misplace.enabled")) return;
handleMisplaceCheck(event, data, player, config);
}
private void handleMisplaceCheck(PacketEvent event, PlayerKnockbackData data,
Player player, FileConfiguration config) {
int entityId = event.getPacket().getIntegers().read(0);
int currentTick = plugin.getServerTick();
// 检查是否是需要防Misplace的实体
if (entityId == data.getLastAttackerId() &&
currentTick - data.getLastHitTick() <= player.getMaximumNoDamageTicks()) {
Player attacker = findPlayerById(entityId);
if (attacker != null && shouldDelayPacket(data, attacker, currentTick, config)) {
delayPacket(event, data, player, config);
}
}
}
private Player findPlayerById(int entityId) {
return Bukkit.getOnlinePlayers().stream()
.filter(p -> p.getEntityId() == entityId)
.findFirst()
.orElse(null);
}
private boolean shouldDelayPacket(PlayerKnockbackData data, Player attacker,
int currentTick, FileConfiguration config) {
PlayerKnockbackData attackerData = plugin.getPlayerDataManager()
.getPlayerData(attacker.getUniqueId());
return attackerData != null &&
currentTick - data.getHitDelay() > attacker.getMaximumNoDamageTicks();
}
private void delayPacket(PacketEvent event, PlayerKnockbackData data,
Player player, FileConfiguration config) {
PacketContainer packet = event.getPacket().shallowClone();
event.setCancelled(true);
data.setServerTick(plugin.getServerTick());
int delayTicks = config.getInt("misplace.delay");
Bukkit.getScheduler().runTaskLater(plugin, () -> {
ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet, false);
}, delayTicks);
}
private FileConfiguration getPlayerConfig(PlayerKnockbackData data) {
ConfigHolder holder = plugin.getConfigManager().getConfigs()
.get(data.getCurrentKBFile());
return holder != null ? (FileConfiguration) holder.getConfig() : null;
}
}

View File

@@ -0,0 +1,157 @@
package me.dw1e.kbm.listeners;
import me.dw1e.kbm.KnockbackManager;
import me.dw1e.kbm.api.KBMPlayerVelocityEvent;
import me.dw1e.kbm.config.ConfigHolder;
import me.dw1e.kbm.data.PlayerKnockbackData;
import me.dw1e.kbm.knockback.KnockbackCalculator;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.player.*;
import org.bukkit.projectiles.ProjectileSource;
import org.bukkit.util.Vector;
public class PlayerEventListener implements Listener {
private final KnockbackManager plugin;
private final KnockbackCalculator knockbackCalculator;
public PlayerEventListener(KnockbackManager plugin) {
this.plugin = plugin;
this.knockbackCalculator = new KnockbackCalculator(plugin);
}
@EventHandler
private void onPlayerMove(PlayerMoveEvent event) {
Location to = event.getTo();
Location from = event.getFrom();
if (to.equals(from)) return;
Player player = event.getPlayer();
PlayerKnockbackData data = plugin.getPlayerDataManager().getPlayerData(player.getUniqueId());
if (data != null && data.checkOnGround()) {
data.setLastGroundY(to.getY());
}
}
@EventHandler
private void onPlayerToggleSprint(PlayerToggleSprintEvent event) {
PlayerKnockbackData data = plugin.getPlayerDataManager()
.getPlayerData(event.getPlayer().getUniqueId());
if (data != null) {
data.setSprinting(event.isSprinting());
}
}
@EventHandler
private void onPlayerCommand(PlayerCommandPreprocessEvent event) {
String message = event.getMessage().toLowerCase();
// 支持的命令列表
String[] supportedCommands = {
"/knockbackmanager:kbm", "/kb", "/kbm", "/knockback",
"/knockbackmanager:kb", "/knockbackmanager:knockback"
};
for (String cmd : supportedCommands) {
if (message.equals(cmd)) {
event.getPlayer().sendMessage(plugin.getPluginPrefix() + ChatColor.GRAY +
"此服务器正在使用 " + ChatColor.WHITE + "Knockback Manager" +
ChatColor.GRAY + "(v" + plugin.getDescription().getVersion() +
") 击退修改插件");
break;
}
}
}
@EventHandler
private void onPlayerQuit(PlayerQuitEvent event) {
plugin.getPlayerDataManager().unregisterPlayer(event.getPlayer().getUniqueId());
}
@EventHandler(priority = EventPriority.LOWEST)
private void onPlayerVelocity(PlayerVelocityEvent event) {
Player player = event.getPlayer();
PlayerKnockbackData data = plugin.getPlayerDataManager().getPlayerData(player.getUniqueId());
if (data != null && data.getPendingVelocity() != null) {
event.setVelocity(data.getPendingVelocity());
data.setPendingVelocity(null);
}
}
@EventHandler
private void onPlayerJoin(PlayerJoinEvent event) {
plugin.getPlayerDataManager().registerPlayer(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
private void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
if (!(event.getEntity() instanceof Player)) return;
Entity damager = event.getDamager();
Player victim = (Player) event.getEntity();
PlayerKnockbackData victimData = plugin.getPlayerDataManager().getPlayerData(victim.getUniqueId());
if (victimData == null || victimData.isFiltered()) return;
// 处理攻击者
Player attacker = extractAttacker(damager);
if (attacker == null) return;
// 检查自伤
boolean isSelfDamage = attacker.equals(victim);
if (isSelfDamage) return;
// 处理击退计算
knockbackCalculator.calculateKnockback(victim, attacker, victimData, damager);
}
private Player extractAttacker(Entity damager) {
if (damager instanceof Player) {
return (Player) damager;
} else if (damager instanceof Projectile) {
ProjectileSource shooter = ((Projectile) damager).getShooter();
return (shooter instanceof Player) ? (Player) shooter : null;
}
return null;
}
@EventHandler(priority = EventPriority.HIGH)
private void onEntityDamage(EntityDamageByEntityEvent event) {
if (!(event.getEntity() instanceof Player)) return;
Player victim = (Player) event.getEntity();
PlayerKnockbackData victimData = plugin.getPlayerDataManager().getPlayerData(victim.getUniqueId());
// 如果玩家被过滤,取消击退
if (victimData != null && victimData.isFiltered()) {
// 重置击退速度
victim.setVelocity(new Vector(0, 0, 0));
}
}
// 处理玩家重生事件,重置数据
@EventHandler
private void onPlayerRespawn(PlayerRespawnEvent event) {
Player player = event.getPlayer();
PlayerKnockbackData data = plugin.getPlayerDataManager().getPlayerData(player.getUniqueId());
if (data != null) {
data.setLastGroundY(player.getLocation().getY());
data.setPendingVelocity(null);
}
}
}

View File

@@ -0,0 +1,16 @@
horizontal:
ground: 0.4
air: 0.4
sprint_extra: 0.5
projectile_multiplier: 1.0
vertical:
ground: 0.36075
air: 0.24775
sprint_extra: 0.1
projectile_multiplier: 1.0
misplace:
enabled: false
delay: 1
stop_sprint: true
y_limit: 0.675
hit_delay: 20

View File

@@ -0,0 +1,18 @@
name: KnockbackManager
main: me.dw1e.kbm.KnockbackManager
version: 1.13.4
author: dw1e
softdepend: [ ProtocolLib ]
api-version: 1.13
commands:
kbm:
aliases: [ kb, knockback ]
permissions:
kbm.use:
description: Allows usage of Knockback Manager commands
default: op
kbm.admin:
description: Full access to Knockback Manager features
default: op