1
This commit is contained in:
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal 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
3
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
7
.idea/encodings.xml
generated
Normal file
7
.idea/encodings.xml
generated
Normal 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
14
.idea/misc.xml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal 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
BIN
libs/ProtocolLib.jar
Normal file
Binary file not shown.
BIN
libs/spigot-1.8.8-R0.1-SNAPSHOT-latest.jar
Normal file
BIN
libs/spigot-1.8.8-R0.1-SNAPSHOT-latest.jar
Normal file
Binary file not shown.
45
pom.xml
Normal file
45
pom.xml
Normal 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>
|
||||||
101
src/main/java/me/dw1e/kbm/KnockbackManager.java
Normal file
101
src/main/java/me/dw1e/kbm/KnockbackManager.java
Normal 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; }
|
||||||
|
}
|
||||||
68
src/main/java/me/dw1e/kbm/KnockbackManagerAPIImpl.java
Normal file
68
src/main/java/me/dw1e/kbm/KnockbackManagerAPIImpl.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/main/java/me/dw1e/kbm/api/KBMPlayerVelocityEvent.java
Normal file
46
src/main/java/me/dw1e/kbm/api/KBMPlayerVelocityEvent.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/main/java/me/dw1e/kbm/api/KnockbackManagerAPI.java
Normal file
14
src/main/java/me/dw1e/kbm/api/KnockbackManagerAPI.java
Normal 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);
|
||||||
|
}
|
||||||
491
src/main/java/me/dw1e/kbm/commands/KBMCommandHandler.java
Normal file
491
src/main/java/me/dw1e/kbm/commands/KBMCommandHandler.java
Normal 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
src/main/java/me/dw1e/kbm/config/ConfigHolder.java
Normal file
21
src/main/java/me/dw1e/kbm/config/ConfigHolder.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
242
src/main/java/me/dw1e/kbm/config/ConfigManager.java
Normal file
242
src/main/java/me/dw1e/kbm/config/ConfigManager.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/main/java/me/dw1e/kbm/data/PlayerDataManager.java
Normal file
40
src/main/java/me/dw1e/kbm/data/PlayerDataManager.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
103
src/main/java/me/dw1e/kbm/data/PlayerKnockbackData.java
Normal file
103
src/main/java/me/dw1e/kbm/data/PlayerKnockbackData.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
158
src/main/java/me/dw1e/kbm/knockback/KnockbackCalculator.java
Normal file
158
src/main/java/me/dw1e/kbm/knockback/KnockbackCalculator.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
106
src/main/java/me/dw1e/kbm/listeners/PacketListener.java
Normal file
106
src/main/java/me/dw1e/kbm/listeners/PacketListener.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
157
src/main/java/me/dw1e/kbm/listeners/PlayerEventListener.java
Normal file
157
src/main/java/me/dw1e/kbm/listeners/PlayerEventListener.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main/resources/default.yml
Normal file
16
src/main/resources/default.yml
Normal 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
|
||||||
18
src/main/resources/plugin.yml
Normal file
18
src/main/resources/plugin.yml
Normal 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
|
||||||
Reference in New Issue
Block a user